-
Notifications
You must be signed in to change notification settings - Fork 48
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
Implement WASI poll_oneoff #716
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking a stab at this!
A few comments to better understand what's going on.
Have you tested this implementation with actual code using it?
Go, Zig and even Rust should produce this call in some circumstances ...
nevents++; | ||
} | ||
} | ||
if (nevents > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reference implementation you started from?
I cannot find this condition in the wazero implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote this myself based on the specification. The wazero implementation seems incorrect as it writes out all clock events, when per the specification it should only be the clocks that reached the timeout:
The time value of clock
subscription_clock::id
has reached timestampsubscription_clock::timeout
.
This condition is actually incorrect, as it will prevent writing out clocks if there were any triggered descriptors. Instead, I'll change loop to skip sleeping if it already wrote events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote this myself based on the specification.
Can you add a comment with the relevant (perma-) link?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just the specification for WASI. We could add links for all the functions as a separate commit if you think that is valuable.
|
||
long minTimeout = clockEvents.stream().mapToLong(Entry::getKey).min().orElse(0); | ||
long start = System.nanoTime(); | ||
while (true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A while (true)
with so much code afterwords make me slightly uncomfortable.
In this case, if I'm not missing an invariant:
nsubscriptions
== 0readEvents
is an empty ListclockEvents
is an empty List- the loop doesn't end
is it the expected behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 I think just a few test cases would be useful to clarify the behavior :)
there's a few here
- unit tests https://github.com/tetratelabs/wazero/blob/610c202ec48f3a7c729f2bf11707330127ab3689/imports/wasi_snapshot_preview1/poll_test.go
- integration:
(it doesn't need to be all, because this is not really covering all of them, just a source of inspiration)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I had a typo on the check at the start. It should be
If
nsubscriptions
is 0, returnserrno::inval
.
So we will never enter the loop in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating this, now it looks safer, in all honesty, I still need to squint a lot my eyes to understand what's going on.
Adding unit tests for the implementation is an option to better clarify the expected behavior, does it sound feasible without a major amount of additional work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added tests for all cases except for invalid input and IOException
, and verified this with code coverage in the IDE. I also added comments which hopefully makes the logic easier to follow.
I verified the implementation with Python, which uses this function for some modules such as |
5921c9a
to
1002328
Compare
Agreed, I'll test both of those above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great improvements here, thanks @electrum for addressing the comments, it reads much better now!
One additional note on the use of System.nanoTime
and I'll wait for the results of running Go and/or Zig 🙂
case WasiClockId.MONOTONIC: | ||
memory.writeLong(resultPtr, System.nanoTime()); | ||
memory.writeLong(resultPtr, clockTime(clockId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to use clockTime
(or something similar) everywhere instead of System.nanotime()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is a helper method that is shared with clock_time_get()
to get the realtime or monotonic clock. The other usages of System.nanoTime()
are in poll_oneoff()
to measure how long we have been sleeping. As I mentioned in the other comment, mocking that seems insufficient as we directly call Thread.sleep()
. I'd need to think more about how those interact.
|
||
@Test | ||
public void wasiPollOneoffClockAbstime() { | ||
long deadline = System.nanoTime() + MILLISECONDS.toNanos(25); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using System.nanotime
it would be great to use a "mocked" clock.
i.e.:
- set a clock source that can be controlled from the test
- trigger
pollOneOff
and verify that the deadline has not been reached - increase the clock to be > than the expected deadline
- verify that the deadline is reached
Do you think it's doable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We support mocking for the realtime Clock
via WasiOptions
. However, I don't think that simply mocking System.nanoTime()
is sufficient, as it interacts with the way we use Thread.sleep()
.
Remember that poll_oneoff()
waits until there is at least one event. So in order to test that a clock event doesn't trigger, we need a different event to trigger. But then we won't actually sleep, unless we mock stdin
with something that starts off unreadable and becomes readable later.
I can look at this more if you feel strongly, but I think the testing we have is sufficient, especially for something that likely won't be used for more than a basic sleep function.
The
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can look at this more if you feel strongly, but I think the testing we have is sufficient, especially for something that likely won't be used for more than a basic sleep function.
I agree with this evaluation, it would be nice to have more isolated tests not using global resources like nanoTime
and sleep
, but the current implementation and testing is definitely sufficient.
LGTM, thanks for the effort and bearing with me.
Additional notes:
- yes please, add the link to the WASI spec you are looking at (I'm always confused about the right link)
- I recall the zig testsuite to progress a bit more, but it can be a number of factors ...
The reference implementation does not support or validate stderr. For stdout, it treats a default specification for a missing JSON file as if the field is not present, rather than an empty string (thus ignoring anything that the test outputs to stdout).
75e6f4e
to
914f67f
Compare
Fixes #592