Skip to content

Corrected handling of self-cancelation within timeout#3453

Merged
djspiewak merged 2 commits intotypelevel:series/3.xfrom
djspiewak:bug/canceled-timeout
Feb 25, 2023
Merged

Corrected handling of self-cancelation within timeout#3453
djspiewak merged 2 commits intotypelevel:series/3.xfrom
djspiewak:bug/canceled-timeout

Conversation

@djspiewak
Copy link
Member

This has to be targeted at 3.5.0 since it requires a law change. In particular, this adjusts the default race semantic such that race(canceled, fa) is now canceled rather than being equal to fa. I think this actually makes a lot more sense.

Fixes #3396

@armanbilge armanbilge self-requested a review February 22, 2023 21:34
Copy link
Member

@armanbilge armanbilge left a comment

Choose a reason for hiding this comment

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

I wasn't sure about this initially, but #3456 and the linked Discord discussion changed my mind.

This also addresses the problem I encountered in #3232 (comment). Embedding the cancelation does seem like the right default, with racePair available for more customized semantics.

@djspiewak djspiewak merged commit ec8b9e0 into typelevel:series/3.x Feb 25, 2023
poll(racePair(fa, sleep(duration))) flatMap {
case Left((oc, f)) =>
poll(f.cancel *> oc.embedNever)
poll(f.cancel *> oc.embed(poll(F.canceled) *> F.never))
Copy link
Contributor

Choose a reason for hiding this comment

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

I was just about to open a bug for the current version (3.4.8), as timeoutAndForget was uncancelable, but then saw that you already fixed it.

I'd like to ask, however, what is the meaning of this line here? And does it work on the current version?

This change is confusing. What's the difference between oc.embed(poll(F.canceled) *> F.never) and oc.embedNever? It feels like this change is needed due to some unintuitive interaction between the operations involved (race, embed and poll).

I am asking because we are migrating a big codebase from Cats-Effect v2, to Cats-Effect v3, and I need custom semantics for our timeout operations, which means our custom timeout implementation. And we need something workable right now.

Thanks for any insight you can give.

Copy link
Member

Choose a reason for hiding this comment

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

What's the difference between oc.embed(poll(F.canceled) *> F.never) and oc.embedNever?

  • oc.embedNever will hang indefinitely if oc is canceled
  • oc.embed(poll(F.canceled) *> F.never) will self-cancel if oc is canceled

Copy link
Contributor

@alexandru alexandru Apr 5, 2023

Choose a reason for hiding this comment

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

Thank you kindly for your answer @armanbilge,

But what's the problem if embedNever hangs? Given it's in the context of poll, IO.never should be cancelable. This is the part that I'm missing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, if I'm generating noise on an issue right now, maybe this should have been asked on the forum.

I wondered if this isn't some mistake, or an API issue. But I may have gaps in how that embed / fold works. Intuitively, embedNever should have worked, but I guess it doesn't have access to that poll.

Copy link
Member

Choose a reason for hiding this comment

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

But what's the problem if embedNever hangs?

Then we end up with problems like #3396.

Given it's in the context of poll, IO.never should be cancelable. This is the part that I'm missing.

Right, it's cancelable, but it will hang unless it is canceled. The semantic we decided we want here is for cancelation to propagate.

Copy link
Contributor

Choose a reason for hiding this comment

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

@armanbilge thanks a lot, I think I get it.

biochimia added a commit to biochimia/fs2-kafka that referenced this pull request Sep 6, 2023
FakeFiber was used by KafkaConsumer to manage:

1. the consumerActor fiber, processing requests (including polls);
2. the pollScheduler fiber, scheduling poll requests (subject to
   backpressure);
3. a fiber combining the above two fibers.

Compared to a regular fiber, FakeFiber offered a method to combine two
fibers by racing them one against the other. The semantics were similar
to the race method for effects, but operating at the fiber level.

In KafkaConsumer, FakeFiber was used with cancellation effects that
returned immediately (i.e., fiber.cancel.start.void). In addition the
fiber outcome was relayed to KafkaConsumer.awaitTermination.

With this change FakeFiber is replaced with an Async[F].race of the
underlying effects. This is managed in the new method
startBackgroundConsumer, which builds upon effects assembled by
runConsumerActor and runPollScheduler. These effects are unwrapped from
the previous fibers.

As before, cancellation of the consumer effect is only waited on in
awaitTermination, where any errors are propagated.

Compared to the original behaviour of FakeFiber.combine, starting with
cats-effect 3.5.0, cancellation of one of the consumer effects will lead
to cancellation of both, as per changes introduced with
typelevel/cats-effect#3453. I'm not sure how
cancelation would come into play here, but the behaviour change looks
appropriate: without one of the racing fibers KafkaConsumer would not be
functional (no polls scheduled, or no requests/polls processed).
biochimia added a commit to biochimia/fs2-kafka that referenced this pull request Sep 6, 2023
FakeFiber was used by KafkaConsumer to manage:

1. the consumerActor fiber, processing requests (including polls);
2. the pollScheduler fiber, scheduling poll requests (subject to
   backpressure);
3. a fiber combining the above two fibers.

Compared to a regular fiber, FakeFiber offered a method to combine two
fibers by racing them one against the other. The semantics were similar
to the race method for effects, but operating at the fiber level.

In KafkaConsumer, FakeFiber was used with cancellation effects that
returned immediately (i.e., fiber.cancel.start.void). In addition the
fiber outcome was relayed to KafkaConsumer.awaitTermination.

With this change FakeFiber is replaced with an Async[F].race of the
underlying effects. This is managed in the new method
startBackgroundConsumer, which builds upon effects assembled by
runConsumerActor and runPollScheduler. These effects are unwrapped from
the previous fibers.

As before, cancellation of the consumer effect is only waited on in
awaitTermination, where any errors are propagated.

Compared to the original behaviour of FakeFiber.combine, starting with
cats-effect 3.5.0, cancellation of one of the consumer effects will lead
to cancellation of both, as per changes introduced with
typelevel/cats-effect#3453. I'm not sure how
cancelation would come into play here, but the behaviour change looks
appropriate: without one of the racing fibers KafkaConsumer would not be
functional (no polls scheduled, or no requests/polls processed).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Timeout on a canceled IO

3 participants