Skip to content

Shouldn't join resume after onCompletion? #627

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

Closed
jcornaz opened this issue Sep 27, 2018 · 7 comments
Closed

Shouldn't join resume after onCompletion? #627

jcornaz opened this issue Sep 27, 2018 · 7 comments
Assignees

Comments

@jcornaz
Copy link
Contributor

jcornaz commented Sep 27, 2018

Hello,

please consider the following code:

fun foo() {
  Thread.sleep(1000)
  println("alright this have been executed")
}

fun main(args: Array<String>) {
  val job = Job()

  job.cancel()

  GlobalScope.launch(job) {
    try {
      println("do stuff")
    } finally {
      foo()
    }
  }

  runBlocking {
    println("before join")
    job.cancelAndJoin()
    println("afterJoin")
  }
}

foo() is not invoked at all, because the job is cancelled before being scheduled.

So what are my options, if I want to make sure foo() is always executed?

One may try to use onCompletion like this:

fun foo() {
  Thread.sleep(1000)
  println("alright this have been executed")
}

fun main(args: Array<String>) {
  val job = Job()

  job.cancel()

  GlobalScope.launch(job, onCompletion = { foo() }) { println("do stuff") }

  runBlocking {
    println("before join")
    job.cancelAndJoin()
    println("afterJoin")
  }
}

But it doesn't help, because join doesn't seem to care if onCompletion have been executed or not.

And here comes my question: Shouldn't join resume only after the execution onCompletion?

@elizarov
Copy link
Contributor

elizarov commented Sep 27, 2018

We have a feature for that — atomic start mode. launch(start = CoroutineStart.ATOMIC) { ... } seems to be doing what you are looking for, isn't it?

@jcornaz
Copy link
Contributor Author

jcornaz commented Sep 27, 2018

Thanks @elizarov. I can, indeed make sure that my example always execute foo.

But I don't really care if println("do stuff") is executed or not. And in fact I'd prefer not execute it if the job has been cancelled.

What I'm saying is that despite that the goal of onCompletion (#279) was to add a way to make sure a resource is closed/cancel even without the need to use CoroutineStart.ATOMIC, this goal is not completely achieved, as onCompletion may not be executed in the (extreme) condition shown in my example.

I'd like to quote yourself (in #279) about the solution of CoroutineStart.ATOMIC:

We can use produce(context, start = CoroutineStart.ATOMIC) { ... } in implementation of all the operators. It makes sure that coroutine is not cancellable until it starts to execute. This change provides an immediate relief for this problem, but it has a slight unintended side-effect that this "non-cancellable" period extends until the first suspension in the coroutine, while might be too long. It also feels extremely fragile. It is quite easy to forget that start parameter.

@jcornaz
Copy link
Contributor Author

jcornaz commented Sep 27, 2018

And still CoroutineStart.ATOMIC or not, I'd expect foo to be able print in console with the following code:

fun foo() {
  Thread.sleep(1000)
  println("alright this have been executed")
}

fun main(args: Array<String>) {
  val job = Job()

  job.cancel()

  GlobalScope.launch(job, start = CoroutineStart.ATOMIC, onCompletion = { foo() }) { println("do stuff") }

  runBlocking {
    println("before join")
    job.cancelAndJoin()
    println("after join")
  }
}

But the result is:

do stuff
before join
after join

And I'd like to get:

before join
alright this have been executed
after join

@elizarov
Copy link
Contributor

elizarov commented Sep 27, 2018

We've deprecated onCompletion (and one of the reasons is the unpredictable order). However, your first example (with try/finally{ foo() }) should work as expected with start = ATOMIC.

@jcornaz
Copy link
Contributor Author

jcornaz commented Sep 27, 2018

However, your first example (with try/finally{ foo() }) should work as expected with start = ATOMIC

Yes indeed.

We've deprecated onCompletion (and one of the reasons is the unpredictable order)

Ok. thanks.

Before I close this issue, may I ask what will be the preferred solution to #279? Shall we use CoroutineStart.ATOMIC ?

@elizarov
Copy link
Contributor

elizarov commented Sep 27, 2018

For now, start = ATOMIC with try / finally { ... } is the only guaranteed way to make sure that something is done before other coroutines that .join resume. In the future we might add back support or something similar to onCompletion, but it has to be composable (don't want to hack every coroutine builder again).

Btw, if you are only interested in exceptions, you can install CorouinteExceptionHandler. It is guaranteed to be invoked before all joining coroutines can resume.

@elizarov
Copy link
Contributor

Ops. We need to promote CoroutineStart.ATOMIC from internal to experimental.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants