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

[core] Suppress harmless ObjectRefStreamEndOfStreamError when using asyncio #37062

Merged

Conversation

stephanie-wang
Copy link
Contributor

Why are these changes needed?

The last ref returned by a streaming generator is a sentinel ObjectRef that contains the end-of-stream error. This suppresses an error from asyncio that the exception is never retrieved (which is expected).

Related issue number

Closes #36956.

Checks

  • I've signed off every commit(by using the -s flag, i.e., git commit -s) in this PR.
  • I've run scripts/format.sh to lint the changes in this PR.
  • I've included any doc changes needed for https://docs.ray.io/en/master/.
    • I've added any new APIs to the API Reference. For example, if I added a
      method in Tune, I've added it in doc/source/tune/api/ under the
      corresponding .rst file.
  • I've made sure the tests are passing. Note that there might be a few flaky tests, see the recent failures at https://flakey-tests.ray.io/
  • Testing Strategy
    • Unit tests
    • Release tests
    • This PR is not tested :(

Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
Copy link
Contributor

@edoakes edoakes left a comment

Choose a reason for hiding this comment

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

Thanks! I'm glad that this is confirmed to be benign :)

Comment on lines 338 to 342
# NOTE(swang): Hack to avoid asyncio warnings about not retrieving the
# exception. The exception will get returned to the user through the
# below code.
for result in ready:
exc = result.exception()
Copy link
Contributor

Choose a reason for hiding this comment

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

This will suppress any form of unexpected exception. I would suggest to explicitly wrap the ref in a task instead and ignore the specific exception type:

async def peek_stream_wrapper(ref: "ImNotSureWhatTypeThisIs"):
    try:
        await ref
    except ObjectRefStreamEndOfStreamError:
        pass # Expected.

peek_stream_task = asyncio.create_task(
    peek_stream_wrapper(core_worker.peek_object_ref_stream(self._generator_ref))
)
ready, unready = await asyncio.wait([peek_stream_task], timeout=timeout_s)

asyncio.wait does this under the hood anyways so there shouldn't be additional overhead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm actually I don't think we should do this. This part of the code is only meant to wait for the ref to become ready; we just don't have a way to do this for asyncio right now without also fetching the data.

The expected use case is that the user will then do something with the returned ref once it's ready. I believe Ray also warns if the ref has an exception and is never read, so we'll be warning twice if we don't suppress the asyncio error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see so this could raise other exceptions aside from ObjectRefStreamEndOfStreamError?

Ideally we would "whitelist" the expected exceptions here -- maybe a conservative way to do it would be except RayError? I'm just a little uneasy about suppressing all exceptions, there may be some corner case that raises an unexpected exception and identifying/debugging it will be much easier if the exception is logged.

Ultimately up to you though, if you don't think this is an issue then suppressing them all is fine. Though it's probably still slightly less hacky to wrap it in a task and catch bare Exception.

Copy link
Contributor Author

@stephanie-wang stephanie-wang Jul 5, 2023

Choose a reason for hiding this comment

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

Yeah, to clarify, the reasoning for suppressing all errors is that at this point, the user hasn't actually tried to receive the error yet. This function is supposed to return the ObjectRef that the user can await, and then at point, asyncio will warn if the user doesn't retrieve the exception.

So either we don't suppress the error here, and the user will always see an extra warning, or we do suppress the error here, and then asyncio or Ray will warn if the user doesn't do anything with the ObjectRef.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it. Thanks for clarifying.

@stephanie-wang stephanie-wang added @author-action-required The PR author is responsible for the next step. Remove tag to send back to the reviewer. and removed @author-action-required The PR author is responsible for the next step. Remove tag to send back to the reviewer. labels Jul 5, 2023
Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
@stephanie-wang stephanie-wang added the @author-action-required The PR author is responsible for the next step. Remove tag to send back to the reviewer. label Jul 5, 2023
Copy link
Contributor

@rkooo567 rkooo567 left a comment

Choose a reason for hiding this comment

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

Some nit commetns!

# The exception will get returned (or warned) to the user once they
# actually await the ref.
try:
await ref
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use exception() instead here? https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.exception

This way, we don't need to await on actual value if there's no exception

Copy link
Contributor Author

Choose a reason for hiding this comment

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

asyncio.wait will await on the actual value anyway right now.

ref = await generator._next_async(timeout_s=0)


@pytest.mark.asyncio
Copy link
Contributor

Choose a reason for hiding this comment

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

are you planning to delete this, or is it temporary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These tests failed afterwards. I took a look and I don't think we really need them (they're testing internal behavior).

@stephanie-wang stephanie-wang merged commit 831c4c5 into ray-project:master Jul 6, 2023
@stephanie-wang stephanie-wang deleted the asyncio-stream-error branch July 6, 2023 14:51
stephanie-wang added a commit to stephanie-wang/ray that referenced this pull request Jul 7, 2023
…syncio (ray-project#37062)

The last ref returned by a streaming generator is a sentinel ObjectRef that contains the end-of-stream error. This suppresses an error from asyncio that the exception is never retrieved (which is expected).
Related issue number

Closes ray-project#36956.

---------

Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
bveeramani pushed a commit that referenced this pull request Jul 7, 2023
…syncio (#37062) (#37200)

The last ref returned by a streaming generator is a sentinel ObjectRef that contains the end-of-stream error. This suppresses an error from asyncio that the exception is never retrieved (which is expected).
Related issue number

Closes #36956.

---------

Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
arvind-chandra pushed a commit to lmco/ray that referenced this pull request Aug 31, 2023
…syncio (ray-project#37062)

The last ref returned by a streaming generator is a sentinel ObjectRef that contains the end-of-stream error. This suppresses an error from asyncio that the exception is never retrieved (which is expected).
Related issue number

Closes ray-project#36956.

---------

Signed-off-by: Stephanie Wang <swang@cs.berkeley.edu>
Signed-off-by: e428265 <arvind.chandramouli@lmco.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@author-action-required The PR author is responsible for the next step. Remove tag to send back to the reviewer.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[streaming] RaySystemError('Unrecognized error type 24') when running basic Serve example
3 participants