title: Always finish Mono.usingWhen on cancel signal
body: <p>we write a reactive WebFlux application that basically gets a resource, does amount of work in closure and at the end unlocks the resource with either initial version or updated one from closure.</p>
  
  <p>Ex:</p>
  
  <pre><code>Mono&lt;ProductLock&gt; lock = service.lock()
  
  Mono.usingWhen(lock,
              (ProductLock state) -&gt; service.doLongOperationAsync(state),
              (ProductLock state) -&gt; service.unlock(state))
  </code></pre>
  
  <p><code>ProductLock</code> is a definition of locked product meaning that we can do operations via HTTP API including multiple microservices.</p>
  
  <p><code>service.doLongOperationAsync()</code> - calls a few HTTP APIs which ARE EXPECTED to ALWAYS be finished once started (failure - is normal, but if operation started - it needs to be finished, because HTTP call cannot be rolled back)</p>
  
  <p><code>service.unlock()</code> - operation MUST be called only after successful or failed execution of doLongOperationAsync.</p>
  
  <p>In happy scenarios everything works as expected: on success product unlocked, on failure also unlocked.</p>
  
  <p>The problems come when client, who calls our service (SOAP UI, POSTMAN, any real client), drops connection or times out - the cancel signal is generated and getting up till the above code.
  At this point, anything within <code>service.doLongOperationAsync</code> is stopped and <code>service.unlock</code> is called on cancel asynchronously.</p>
  
  <p>Question: how can we prevent this from happening.
  The requirements are: </p>
  
  <ol>
  <li>once <code>doLongOperationAsync</code> started - it must finish</li>
  <li><code>service.unlock(state)</code> - must be called ONLY after <code>doLongOperationAsync</code>, even on cancel.</li>
  </ol>
  
  <p><a href="https://1drv.ms/u/s!AgWtih1nTVQYpL9KrotUjnz_ULXvnw?e=iURkJa" rel="nofollow noreferrer">Spring Boot repro</a></p>
  
  <p>MRE:</p>
  
  <pre><code>@Bean
  public RouterFunction&lt;ServerResponse&gt; route() {
      return RouterFunctions.route().GET("/work", request -&gt; processRequest()
          .flatMap(res -&gt; ServerResponse.ok().bodyValue(res))).build();
  }
  
  private Mono&lt;String&gt; processRequest() {
      //Need unlock to execute exactly after doWorkAsync in any case
      return Mono.usingWhen(lock(), this::doWorkAsync, this::unlock)
          .doOnNext((id) -&gt; System.out.println("Request processed:" + id))
          .doOnCancel(() -&gt; System.out.println("Request cancelled"));
  }
  
  private Mono&lt;UUID&gt; lock() {
      return Mono.defer(() -&gt; Mono.just(UUID.randomUUID())
          .doOnNext(id -&gt; System.out.println("Locked:" + id)));
  }
  
  //Need this to finish no matter what
  private Mono&lt;String&gt; doWorkAsync(UUID lockID) {
      return Mono.just(lockID).map(UUID::toString)
          .doOnNext(id -&gt; System.out.println("Start working on:" + lockID))
          .delayElement(Duration.ofSeconds(10))
          .doOnNext(id -&gt; System.out.println("Finished work on:" + id))
          // Should never be called
          .doOnCancel(() -&gt; System.out.println("Processing cancelled:" + lockID));
  }
  
  private Mono&lt;Void&gt; unlock(UUID lockID) {
      return Mono.fromRunnable(() -&gt; System.out.println("Unlocking:" + lockID));
  }
  </code></pre>