Skip to content

Avoid buffered I/O #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 9, 2021
Merged

Avoid buffered I/O #4

merged 1 commit into from
Jun 9, 2021

Conversation

RinHizakura
Copy link
Contributor

Here's a macro solution to avoid unexpected output by the buffered I/O printf. It could have some limitations and need improvement, but it achieves the purpose of output in text lines as we may want.

fiber/fiber.c Outdated
@@ -119,13 +119,24 @@ int fiber_wait_all()
return FIBER_NOERROR;
}

/* A simple solution to avoid unexpected output by the buffered I/O printf is
Copy link
Contributor

Choose a reason for hiding this comment

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

You shall explain why this wrapper is "safe."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I am thinking to change the name for the macro. I use the word "safe" to represent that the output can show as we may want. But as far as I know, printf is said to be thread-safe in the Linux manual. That means it won't corrupt anything when using in the multithreading. However, some interleaving that doesn't change the outcome is still allowed to happen, which can produce the mixing output between threads.

So I am not sure if the name print_safe will mislead someone to the concept that printf is not thread-safe. Do I misunderstand something or any advice for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

It was meant to be "safe." Instead, its intention is to eliminate the impact of buffered I/O, which causes the unexpected sequence of strings.

Copy link
Contributor

@jserv jserv Jun 9, 2021

Choose a reason for hiding this comment

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

While the printf provided by glibc is a buffered output function, the proposed change does "direct" format string manipulation. Therefore, you can rename printf_safe to printf_unbuffered and then use #define printf printf_unbuffered for build-time substitution.

Reference: CS:APP Chapter 10

@RinHizakura RinHizakura force-pushed the pr_fiber branch 3 times, most recently from c949298 to 521032e Compare June 9, 2021 14:27
fiber/fiber.c Outdated
/* A simple solution to avoid unexpected output by the buffered I/O 'printf' is
* using low-level I/O interface 'write'. It works because 'printf' will wait to
* write STDOUT until the buffer is full, or on some other conditions. Using
* write, however, can write STDOUT immediately.
Copy link
Contributor

Choose a reason for hiding this comment

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

Change "write STDOUT" to "write to STDOUT"

@RinHizakura
Copy link
Contributor Author

When I try to go deep on my solution for the interleaved output, I think I may have some wrong thoughts when I design this macro. I first used this as the solution because I think write won't be interrupted except the signal handler during the execution, so it can output the whole given string immediately without interleaving. But soon after some exploration and thinking, I doubt if I have to handle the possibility that write may write less than the request n bytes.

Although for my result, the output contents do show as expected, but I'm not sure if this really solves the problem. I may need time to clarify my own question.

@jserv jserv changed the title Add safe print for fiber Avoid buffered I/O Jun 9, 2021
@RinHizakura RinHizakura force-pushed the pr_fiber branch 2 times, most recently from fd082b4 to 4f4031a Compare June 9, 2021 16:44
A simple solution to avoid unexpected output by the buffered I/O 'printf' is
using low-level I/O interface 'write'. It works because 'printf' will wait to
write to STDOUT until the buffer is full, or on some other conditions. Using
write, however, can write to STDOUT immediately.

Here is a naive implementation for the idea with some limitation and weakness
that need improvement:
1. It will fail if the formatted string with length >64
2. The function 'write' can write less than n bytes. It will need further
   handling if happens.
@jserv jserv merged commit 187c5aa into sysprog21:master Jun 9, 2021
@RinHizakura RinHizakura deleted the pr_fiber branch August 7, 2021 19:19
linD026 added a commit to linD026/concurrent-programs that referenced this pull request Apr 22, 2023
Currently, the reader_func is counting the number of grace periods based
on the value of dut, which is updated by mthpc_rcu_replace_pointer.
However, the value of dut actually represents the time we update the
value, not the number of grace periods.

Also, the original method might result in incorrect counting if someone
tried to update the gp_idx while others who saw the same dut value with
prev_count still depend on the old gp_idx to increase the counter.

To fix the problem, instead of relying on the dut value to increase the
gp_idx, we manually increase gp_idx on write side. Then, we can easily
determine the gp on read side.

For dut value, we simply check the old count value is not greater than
the newest one.

Additionally, since synchronize_rcu is quite slow, readers generally
will pass through the critical section during the first grace period.
To generate more realistic output, we add a delay on read side before
entering the critical section.

Before:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]  100 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    0 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    0 reader(s)
[grace period sysprog21#5]    0 reader(s)

After, we added a delay:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]   76 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    1 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    3 reader(s)
[grace period sysprog21#5]   20 reader(s)
linD026 added a commit to linD026/concurrent-programs that referenced this pull request Apr 22, 2023
Currently, the reader_func is counting the number of grace periods based
on the value of dut, which is updated by rcu_assign_pointer. However, the
value of dut actually represents the time we update the value, not the
number of grace periods.

Also, the original method might result in incorrect counting if someone
tried to update the gp_idx while others who saw the same dut value with
prev_count still depend on the old gp_idx to increase the counter.

To fix the problem, instead of relying on the dut value to increase the
gp_idx, we manually increase gp_idx on write side. Then, we can easily
determine the gp on read side.

For dut value, we simply check the old count value is not greater than
the newest one.

Additionally, since synchronize_rcu is quite slow, readers generally
will pass through the critical section during the first grace period.
To generate more realistic output, we add a delay on read side before
entering the critical section.

Before:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]  100 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    0 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    0 reader(s)
[grace period sysprog21#5]    0 reader(s)

After, we added a delay:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]   76 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    1 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    3 reader(s)
[grace period sysprog21#5]   20 reader(s)
linD026 added a commit to linD026/concurrent-programs that referenced this pull request Apr 22, 2023
Currently, the reader_func is counting the number of grace periods based
on the value of dut, which is updated by rcu_assign_pointer. However,
the value of dut actually represents the time we update the value, not
the number of grace periods.

Also, the original method might result in incorrect counting if someone
tried to update the gp_idx while others who saw the same dut value with
prev_count still depend on the old gp_idx to increase the counter.

To fix the problem, instead of relying on the dut value to increase the
gp_idx, we manually increase gp_idx on write side. Then, we can easily
determine the gp on read side.

For dut value, we simply check the old count value is not greater than
the newest one.

Additionally, since synchronize_rcu is quite slow, readers generally
will pass through the critical section during the first grace period.
To generate more realistic output, we add a delay on read side before
entering the critical section.

Before:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]  100 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    0 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    0 reader(s)
[grace period sysprog21#5]    0 reader(s)

After, we added a delay:

100 reader(s), 5 update run(s), 6 grace period(s)
[grace period #0]   76 reader(s)
[grace period sysprog21#1]    0 reader(s)
[grace period sysprog21#2]    1 reader(s)
[grace period sysprog21#3]    0 reader(s)
[grace period sysprog21#4]    3 reader(s)
[grace period sysprog21#5]   20 reader(s)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants