fix(channel): split intermediate and init readiness channels#3504
Conversation
|
Thank you for the fix. Would it be possible to share the actual reproduction steps? I understand that the intermediate process and the init process run concurrently, but once the init process has been cloned, the intermediate process seems to have very little left to do before sending So I’d like to better understand under what kind of real-world scenario or environment this was observed. As for the fix itself, I’d also like to see a unit test added for this scenario. |
I run And the reason is: I suspect it's because it's running inside a VM, which increases the probability of this happening. |
3d0d8d4 to
dc061a1
Compare
|
Is this an issue that can also be reproduced in |
|
My understanding is that this issue does not occur in In contrast, in youki’s implementation, the main receiver handles messages from both the intermediate and init processes over the same channel, which I believe is what leads to this issue. |
|
Thank you for the comment. I think this PR may be acceptable as an immediate fix, assuming the tests pass. However, I think the same ordering issue could also happen with messages other than As nayuta723 mentioned, youki currently receives messages from both the intermediate process and the init process through the shared MainReceiver. To fix this more permanently, I think we should split the communication channels, for example by using separate intermediate-to-main and init-to-main channels. |
I agree with you. I can also provide a permanent fix. |
183de45 to
2cae7e7
Compare
|
@saku3 @nayuta723 Hi, I've submitted a new version that splits the channels. I've verified it in Boxlite's tests, but I haven't added the corresponding tests to the Youki repository yet. I thought it is difficult to design a test to reproduce this problem. Do you have any good suggestions? |
|
Thank you! In my opinion, we don't need to add tests for asynchronous edge cases like CPU pressure, etc. I believe that for this PR, ensuring all existing tests pass and sharing the manual verification results on Boxlite under 'Tested manually (please provide steps)' should be sufficient. @saku3 |
|
I agree with nayuta723’s suggestion. |
saku3
left a comment
There was a problem hiding this comment.
Thanks, that makes sense. If you have a chance, could you update both the PR title and the commit message to reflect the current implementation?
Under high concurrency, the init process can send InitReady before the intermediate process sends IntermediateReady. The main process used to wait for both messages on the same receiver, so wait_for_intermediate_ready() could receive InitReady first and fail with an unexpected message error. Add separate main-facing receivers for intermediate-owned and init-owned readiness messages. IntermediateReady is read from the intermediate receiver, while InitReady is forwarded through the init receiver and remains available until wait_for_init_ready() consumes it. Signed-off-by: Wenyu Huang <huangwenyuu@outlook.com>
2cae7e7 to
519c459
Compare
Description
Under high concurrency, the init process can send InitReady before the
intermediate process sends IntermediateReady. The main process used to wait
for both messages on the same receiver, so wait_for_intermediate_ready()
could receive InitReady first and fail with an unexpected message error.
Add separate main-facing receivers for intermediate-owned and init-owned
readiness messages. IntermediateReady is read from the intermediate receiver,
while InitReady is forwarded through the init receiver and remains available
until wait_for_init_ready() consumes it.
Type of Change
Testing
make test.Related Issues
ContainerBuilder::build() fails intermittently with:
received unexpected message: InitReady, expected: IntermediateReady(0)
The error comes from libcontainer::process::channel::MainReceiver::wait_for_intermediate_ready().
Normal message flow
During container creation, libcontainer spawns two child processes:
They communicate with the parent via a SOCK_SEQPACKET socket:
Root cause: parallel scheduling race
The init process is forked by the intermediate process using clone3(CLONE_PARENT). The two processes run in parallel. Under CPU pressure, the scheduler may execute the init process faster than the intermediate process. Consequently, InitReady can arrive at the parent before IntermediateReady.
wait_for_intermediate_ready() only calls recv() once. When it sees InitReady, it treats it as an unexpected message and aborts the build
Additional Context