Skip to content

lxc/file_utils: ensure complete data write in lxc_write_nointr#4597

Closed
DreamConnected wants to merge 1 commit intolxc:mainfrom
Container-On-Android:features
Closed

lxc/file_utils: ensure complete data write in lxc_write_nointr#4597
DreamConnected wants to merge 1 commit intolxc:mainfrom
Container-On-Android:features

Conversation

@DreamConnected
Copy link
Contributor

@DreamConnected DreamConnected commented Oct 26, 2025

Previously, lxc_write_nointr could return without writing all data when write() returned EAGAIN/EWOULDBLOCK due to buffer full conditions. 

This change:

  • Implements a loop to continue writing until all data is sent
  • Handles EINTR, EAGAIN, and EWOULDBLOCK errors appropriately
  • Uses poll() to wait for fd to become ready when blocked

Fixed #4546

@DreamConnected DreamConnected force-pushed the features branch 2 times, most recently from 2e8bdd6 to 8003f8f Compare October 26, 2025 05:57
@mihalicyn mihalicyn assigned mihalicyn and unassigned mihalicyn Oct 26, 2025
@mihalicyn mihalicyn self-requested a review October 26, 2025 20:30
@DreamConnected
Copy link
Contributor Author

@stgraber Can anyone review it? @mihalicyn seems too busy to handle.

if (errno == EINTR)
continue;

if (errno == EWOULDBLOCK) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can you explain how you figured that there is a problem.
Do you have a pointer to a specific lxc_write_nointr() call site where this error occurs?
AFAIK, EWOULDBLOCK can only happen with sockets.

From commit message:

Previously, lxc_write_nointr could return without writing all data
when write() returned EAGAIN/EWOULDBLOCK due to buffer full conditions.

I guess this is about sockets, right? We have only a few cases when we call this function on a socket fd and buffer is tiny. Also, in case of EAGAIN/EWOULDBLOCK error, function returns an error, i.e. there is no silent data corruption.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry to reply now, because I didn't bring the test machine with me on business trip.

To get back to the point, the call to EAGAIN/EWOULDBLOCK that reported an error was located at lxc_terminal_ptx_io. When lxc was attached:

  • Terminal fd is changed to non-blocking. lxc_terminal_mainloop_add
  • the amount of data was large, and the output exceeded the size of the terminal buffer (1024 bytes). Defined: #define LXC_TERMINAL_BUFFER_SIZE 1024
  • Continuous output: the command continuously generated data, not completed at one time.
  • Real time: real-time display or continuous monitoring is required.

For example, dd if=/dev/random bs=1000000 count=1 status=none | base64 mentioned in issue. Most easily triggered with pipe |.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! I think we should try to make a test for this scenario. Let's think about this a bit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @DreamConnected,

I've spend more time with this issue and the more I look, the more I feel that I'm that bad guy who introduced this regression in this commit 715fb4e.

Back then, there was an issue with io_uring and there were several attempts to properly handle this stuff. I believe, that clean and correct solution is 7fd671d, while I claimed in the comment that this is a workaround, in fact, using oneshot-polling mode is the only option there. If we use oneshot mode with io_uring, then making FD-s non-blocking seems unnecessary.

Another point is that the logic you've introduced in lxc_write_nointr basically implements blocking write on a non-blocking FD, right? But why we can't just make FD blocking if we really need this blocking semantic anyways?

My proposal is to take 715fb4e and revert it, then check if this helps with the issue we are trying to fix and at the same time doesn't break io_uring scenario (when LXC is built with -Dio-uring-event-loop=true). It would be awesome if you try this, cause you've already spend a lot of time researching this issue and preparing a fix, and I don't want to take this initiative from you. If this revert helps and doesn't break anything else, then you can submit this revert as your fix. ;)

Feel free to disagree or correct me if I'm wrong ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mihalicyn,
Right. Revert the commit 715fb4e is a good solution, which may fundamentally solve this issue by causing the terminal fd to return to blocking. But I need some time to make sure this revert won't affect #4304

Copy link
Member

@mihalicyn mihalicyn Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I need some time to make sure this revert won't affect #4304

No worries about that part. I gonna re-introduce CI tests for io-uring case (see #4631), so we will be able to validate this part. I wonder if this revert will help with data corruption issue (it should theoretically, but Software Engineering as Physics are experimental sciences ;-) ).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DreamConnected you can rebase this on top of recent main because we've merged #4631 this should give us some confidence that revert doesn't regress anything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.revents = 0
};

return poll(&pfd, 1, timeout);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we need a proper error handling on poll() syscall too. It returns negative value in case of error (including EINTR btw.). In current approach, we don't distinguish between error and "normal" scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we need a proper error handling on syscall too. It returns negative value in case of error (including EINTR btw.). In current approach, we don't distinguish between error and "normal" scenarios.poll()

The processing of this part is really too simple. I will submit new changes after testing.

@mihalicyn
Copy link
Member

Hi @DreamConnected,

thanks for working on this. AFAIU, this is an attempt to fix #4546.
Can you confirm that you was able to reproduce this specific issue from #4546?
Can you confirm that with this patches applied, issue is not reproducible anymore?

(I'm very sorry for a super long delay with review.)

Hi @laanwj,

thanks for reporting an issue. As far as I see, you expressed interest to test this change here:
#4546 (comment)

Please, can you clone this branch, compile LXC and test it to confirm if this change helps to solve the problem for you.

Kind regards,
Alex

Previously, lxc_write_nointr could return without writing all data
when write() returned EAGAIN/EWOULDBLOCK due to buffer full conditions.

This change:
- Implements a loop to continue writing until all data is sent
- Handles EINTR, EAGAIN, and EWOULDBLOCK errors appropriately
- Uses poll() to wait for fd to become ready when blocked
- Maintains backward compatibility while fixing partial write issues

Signed-off-by: DreamConnected <[email protected]>
@laanwj
Copy link

laanwj commented Jan 20, 2026

Sorry for the late response.

i've tested this PR (commit 1e1ed6e) and can confirm it fixes the issue i reported in #4546.

i've checked in two ways, inside a lxc-attach session:

  • Open neovim on various large text files. The problem i reported that the output gets broken off halfway does no longer happen.
  • Check using dd if=/dev/urandom bs=1 count=5000000 status=none | base64. No lines are cut off anymore.

Thank you!

@mihalicyn
Copy link
Member

Hey @laanwj,

thanks for testing this!

I was busy with this code and also have prepared an automated test that can recreate a data corruption scenario on CI.
What I've found is that current PR fixes data corruption, but there is something else that makes test to stuck sometimes. It looks like another one and very close bug. I'm digging further.

@mihalicyn
Copy link
Member

Replaced by #4633

@mihalicyn mihalicyn closed this Jan 22, 2026
@DreamConnected
Copy link
Contributor Author

Replaced by #4633

OK, there seems to be a problem with the GitHub API. My login token is banned every few minutes (401).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

lxc-attach drops output with lots of console output

3 participants