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

feat: optional async ops #3715

Closed
wants to merge 4 commits into from
Closed

Conversation

kt3k
Copy link
Member

@kt3k kt3k commented Jan 19, 2020

Motivation

As described in #3694, it's necessary to end the program even when certain types of async ops are remaining in pending_ops list. The example of such op is op_poll_signal in #3610 (which not yet landed)

Solution

This PR enhances deno_core::Op::Async type. Now Op::Async has the flag blocks_exit along with its main future. The flag is used when checking the result of poll method of deno_core::Isolate and if the flag is false (= the op is optional), those ops are filtered out in calculation of Poll result, which means the program can exit with those optional ops still remaining.


closes #3694

@kt3k kt3k force-pushed the feature/optional-async-ops branch from 1d2fb20 to 2828ec4 Compare January 19, 2020 06:13
// We're idle if pending_ops is empty.
if inner.pending_ops.is_empty() {
// We're idle if all pending_ops have blocks_exit flag false.
if inner.pending_ops.iter().all(|op| !op.1) {
Copy link
Contributor

@kevinkassimo kevinkassimo Jan 19, 2020

Choose a reason for hiding this comment

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

Not so sure if this would have some impact under large amount of concurrent async op (iterating potentially thousands of pending ops for each poll).

When I did my impl in #2735 , I used a simple counter: whenever optional op added, increment; when done, decrement; when length of pending ops is same as optional ops counter amount, we know all the remaining are optional ones and can safely exit.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a todo comment for it.

I agree that this could cause perf issue in future, but I think it's not a huge problem for a moment because there is only a few optional async ops (0 at this moment, and 1 if #3610 landed).

Do you think that we need to address it before landing this?

@bartlomieju
Copy link
Member

@kt3k I'm not sure I understand why we need "optional" ops. Could you show some example?

@kevinkassimo
Copy link
Contributor

@bartlomieju It is mostly for signals. We want to allow promises for signal handlers to be resolved when a signal is triggered, but that this promise should not block the process from exit if everything else is done, thus "optional".

@bartlomieju
Copy link
Member

@kevinkassimo so say I have this script:

const sig = Deno.signal(Deno.Signal.SIGTERM);

for await (const _ for sig) {
  console.log("SIGTERM received!");
}

Should this op be optional? That means program would just quit after registering for signal.

@kevinkassimo
Copy link
Contributor

kevinkassimo commented Jan 19, 2020

@bartlomieju Treating signal ops as non-optional blocks exit, which I believe is not what people would expect with signal handlers.

But I do feel using async iterator pattern and have an optional async op is definitely strange, which was one of the reason when I originally did my old PR I implemented sigaction with listener styled API.

@kt3k
Copy link
Member Author

kt3k commented Jan 19, 2020

@bartlomieju @kevinkassimo
I even think Bartek's example is ok to exit immediately because signal handling can never be a main purpose of the program. The user must have some other "main things" other than signal handling, and that "main thing" would block the program to exit.

And actually if we block the program to exit with for await (const _ for sig) {-loop, then we always need to call sig.dispose() to exit the for-await loop at the end of the main process. I think that is not what the users would expect.

tools/benchmark.py Outdated Show resolved Hide resolved
Copy link
Member

@ry ry left a comment

Choose a reason for hiding this comment

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

I like that this PR does not make any changes to the JS interface.

@piscisaureus
Copy link
Member

I like that this PR does not make any changes to the JS interface.

Although sensible defaults nice, I would rather give the end user control over what ops are "optional" vs non-optional.
E.g. there are other ops that one might not want to wait for (e.g. a time-out timer, or a server that implements a debug interface).

@bartlomieju
Copy link
Member

I think @kevinkassimo's implementation with 3-variant Op enum is a bit cleaner.

enum Op {
  Sync(..),
  Async(..),
  AsyncOptional(..)
}

I like that this PR does not make any changes to the JS interface.

Although sensible defaults nice, I would rather give the end user control over what ops are "optional" vs non-optional.
E.g. there are other ops that one might not want to wait for (e.g. a time-out timer, or a server that implements a debug interface).

Combined with above op we can simply add new field to JSON dispatch to handle such scenarios 👍

Comment on lines -514 to 519
Op::Async(fut) => {
Op::Async(fut, blocks_exit) => {
let fut2 = fut.map_ok(move |buf| (op_id, buf));
self.pending_ops.push(fut2.boxed());
self
.pending_ops
.push(Pin::new(Box::new(PendingOp(fut2.boxed(), blocks_exit))));
self.have_unpolled_ops = true;
Copy link
Member

Choose a reason for hiding this comment

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

I'd recommend using Op::Async and Op::AsyncOptional.

Op::AsyncOptional should be kept in Isolate.pending_optional_ops - which is the same type as Isolate.pending_ops.

Copy link
Member Author

Choose a reason for hiding this comment

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

I created another version of this at #3721, which uses AsyncOptional instead of adding flag to Async. I'd like to avoid having Isolate.pending_optional_ops because if we have 2 FuturesUnordered list in isolate, it seems to me that it's difficult to poll these 2 futures list correctly.

@kt3k kt3k mentioned this pull request Jan 20, 2020
@ry
Copy link
Member

ry commented Jan 20, 2020

closed in favor of #3721

@ry ry closed this Jan 20, 2020
@kt3k kt3k deleted the feature/optional-async-ops branch January 21, 2020 17:18
@kt3k kt3k mentioned this pull request Jan 23, 2020
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.

Need to exit the program even when certain async ops are still reamianing
5 participants