Skip to content
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

Merged
merged 2 commits into from
Dec 24, 2024
Merged

Implement WASI poll_oneoff #716

merged 2 commits into from
Dec 24, 2024

Conversation

electrum
Copy link
Collaborator

@electrum electrum commented Dec 18, 2024

Fixes #592

Copy link
Collaborator

@andreaTP andreaTP left a 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) {
Copy link
Collaborator

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.

Copy link
Collaborator Author

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 timestamp subscription_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.

Copy link
Collaborator

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?

Copy link
Collaborator Author

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) {
Copy link
Collaborator

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 == 0
  • readEvents is an empty List
  • clockEvents is an empty List
  • the loop doesn't end

is it the expected behavior?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

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, returns errno::inval.

So we will never enter the loop in that case.

Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

@electrum
Copy link
Collaborator Author

I verified the implementation with Python, which uses this function for some modules such as socket (which fail later on but call this function first).

@andreaTP
Copy link
Collaborator

I verified the implementation with Python

Thanks!
Before merging, for diligence, we should test on one additional language.

On top of my head, those are the easiest to try:

@electrum electrum force-pushed the wasi-poll branch 2 times, most recently from 5921c9a to 1002328 Compare December 20, 2024 09:42
@electrum
Copy link
Collaborator Author

Agreed, I'll test both of those above.

Copy link
Collaborator

@andreaTP andreaTP left a 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));
Copy link
Collaborator

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()?

Copy link
Collaborator Author

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);
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

@electrum
Copy link
Collaborator Author

The c2w test works. The Zig test runs until this point:

716/2552 test.GUID... OK
717/2552 test.chdir smoke test... SKIP
718/2552 test.open smoke test... SKIP

com.dylibso.chicory.wasm.UninstantiableException: Trapped on unreachable instruction

	at com.dylibso.chicory.runtime.Instance.initialize(Instance.java:159)
	at com.dylibso.chicory.runtime.Instance.<init>(Instance.java:100)
	at com.dylibso.chicory.runtime.Instance$Builder.build(Instance.java:722)
	at com.dylibso.chicory.wasi.WasiPreview1Test.shouldRunZigStdlibTestsuite(WasiPreview1Test.java:239)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1597)
Caused by: com.dylibso.chicory.runtime.TrapException: Trapped on unreachable instruction
	at com.dylibso.chicory.runtime.InterpreterMachine.eval(InterpreterMachine.java:150)
	at com.dylibso.chicory.runtime.InterpreterMachine.call(InterpreterMachine.java:81)
	at com.dylibso.chicory.runtime.InterpreterMachine.CALL(InterpreterMachine.java:1637)
	...

@electrum electrum requested a review from andreaTP December 23, 2024 10:08
Copy link
Collaborator

@andreaTP andreaTP left a 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).
@electrum electrum force-pushed the wasi-poll branch 2 times, most recently from 75e6f4e to 914f67f Compare December 24, 2024 04:30
@electrum electrum merged commit c09374c into dylibso:main Dec 24, 2024
15 checks passed
@electrum electrum deleted the wasi-poll branch December 24, 2024 04:38
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.

Implement poll_oneoff in WASI P1
3 participants