<div dir="ltr">I have some code for handling file uploads. It uses FormDataProcessor and tries to do everything asynchronously. I call &quot;FormDataParser.parse&quot; passing in another handler. I&#39;ll call that handler the OnFormDataAvailable. The OnFormDataAvailable handler checks to see if it&#39;s on the IO thread. If it is, it calls dispatch. Either way, once we&#39;re sure we&#39;re dispatched that handler calls yet another handler.<div><br></div><div>What I&#39;ve seen is that my OnFormDataAvailable handler (the one called by parse()) is not on the IO thread so it doesn&#39;t need to call dispatch. And yet, the exchange gets ended before the handler it calls is even close to complete.</div><div><br></div><div>I&#39;ve found a fix, but I don&#39;t understand why it&#39;s necessary. Specifically, if OnFormDataAvailable is not on the IO thread when its invoked it calls the 0-argument version of &quot;HttpServerExchange.dispatch()&quot; (which you&#39;ve told me in another conversation shouldn&#39;t ever be necessary). If I do that, everything is fine.</div><div><br></div><div>For completeness, here&#39;s the complete code:</div><div><br></div><div><pre style="font-family:menlo;font-size:9pt"><span style="color:rgb(0,0,128);font-weight:bold">public class </span>FormDataParsingHandler {<br>  <span style="color:rgb(0,0,128);font-weight:bold">private static final </span>Logger <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log </span>= LoggerFactory.<span style="font-style:italic">getLogger</span>(FormDataParsingHandler.<span style="color:rgb(0,0,128);font-weight:bold">class</span>);<br>  <span style="color:rgb(0,0,128);font-weight:bold">private static final </span>FormParserFactory <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">formParserFactory </span>= FormParserFactory.<span style="font-style:italic">builder</span>().build();<br>  <span style="color:rgb(0,0,128);font-weight:bold">public static final </span>AttachmentKey&lt;FormData&gt; <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">FORM_DATA_ATTACHMENT_KEY </span>= AttachmentKey.<span style="font-style:italic">create</span>(FormData.<span style="color:rgb(0,0,128);font-weight:bold">class</span>);<br><br>  <span style="color:rgb(128,128,128);font-style:italic">/**<br></span><span style="color:rgb(128,128,128);font-style:italic">   * The only public method - this is what gets exposed as the HttpHandler.<br></span><span style="color:rgb(128,128,128);font-style:italic">   */<br></span><span style="color:rgb(128,128,128);font-style:italic">  </span><span style="color:rgb(0,0,128);font-weight:bold">public </span>CompletableFuture&lt;FormData&gt; parseForm(HttpServerExchange exchange) {<br>    <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log</span>.info(<span style="color:rgb(0,128,0);font-weight:bold">&quot;audio file upload request received.&quot;</span>);<br>    FormDataParser parser = <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">formParserFactory</span>.createParser(exchange);<br>    <span style="color:rgb(0,0,128);font-weight:bold">if </span>(parser == <span style="color:rgb(0,0,128);font-weight:bold">null</span>) {<br>      <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log</span>.warn(<span style="color:rgb(0,128,0);font-weight:bold">&quot;No parser found that can handle this content type. Headers were: {}&quot;</span>, exchange.getRequestHeaders());<br>      <span style="color:rgb(0,0,128);font-weight:bold">throw new </span>UserVisibleException(<span style="color:rgb(0,128,0);font-weight:bold">&quot;No parser for the given content type.&quot;</span>, ResponseCodes.<span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">BAD_REQUEST</span>);<br>    }<br><br>    CompletableFuture&lt;FormData&gt; toComplete = <span style="color:rgb(0,0,128);font-weight:bold">new </span>CompletableFuture&lt;&gt;();<br>    <span style="color:rgb(0,0,128);font-weight:bold">try </span>{<br>      parser.parse(<span style="color:rgb(0,0,128);font-weight:bold">new </span>OnFormDataAvailable(toComplete));<br>    } <span style="color:rgb(0,0,128);font-weight:bold">catch </span>(Exception e) {<br>      <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log</span>.error(<span style="color:rgb(0,128,0);font-weight:bold">&quot;Error parsing form data:&quot;</span>, e);<br>      <span style="color:rgb(0,0,128);font-weight:bold">throw </span><span style="font-style:italic">wrapAsUnchecked</span>(e);<br>    }<br><br>    exchange.addExchangeCompleteListener((ex, nextListener) -&gt; {<br>      <span style="color:rgb(128,128,128);font-style:italic">// Must close the parser so it can free any temporary files that were created.<br></span><span style="color:rgb(128,128,128);font-style:italic">      </span><span style="color:rgb(0,0,128);font-weight:bold">try </span>{<br>        <span style="color:rgb(102,14,122)">parser</span>.close();<br>      } <span style="color:rgb(0,0,128);font-weight:bold">catch </span>(IOException e) {<br>        <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log</span>.error(<span style="color:rgb(0,128,0);font-weight:bold">&quot;Error closing the FormDataParser. Request was handled successfully but temporary files may not &quot;<br></span><span style="color:rgb(0,128,0);font-weight:bold">            </span>+ <span style="color:rgb(0,128,0);font-weight:bold">&quot;have been cleaned up.&quot;</span>, e);<br>      }<br>      nextListener.proceed();<br>    });<br><br>    <span style="color:rgb(0,0,128);font-weight:bold">return </span>toComplete;<br>  }<br><br>  <span style="color:rgb(128,128,128);font-style:italic">// The FormDataParser calls an HttpHandler when it&#39;s complete so we add a silly handler here that does nothing but<br></span><span style="color:rgb(128,128,128);font-style:italic">  // complete this method&#39;s future when the form data is available.<br></span><span style="color:rgb(128,128,128);font-style:italic">  </span><span style="color:rgb(0,0,128);font-weight:bold">private static class </span>OnFormDataAvailable <span style="color:rgb(0,0,128);font-weight:bold">implements </span>HttpHandler {<br>    <span style="color:rgb(0,0,128);font-weight:bold">private final </span>CompletableFuture&lt;FormData&gt; <span style="color:rgb(102,14,122);font-weight:bold">toComplete</span>;<br><br>    <span style="color:rgb(0,0,128);font-weight:bold">private </span>OnFormDataAvailable(CompletableFuture&lt;FormData&gt; toComplete) {<br>      <span style="color:rgb(0,0,128);font-weight:bold">this</span>.<span style="color:rgb(102,14,122);font-weight:bold">toComplete </span>= toComplete;<br>    }<br><br>    <span style="color:rgb(128,128,0)">@Override<br></span><span style="color:rgb(128,128,0)">    </span><span style="color:rgb(0,0,128);font-weight:bold">public void </span>handleRequest(HttpServerExchange exchange) <span style="color:rgb(0,0,128);font-weight:bold">throws </span>Exception {<br>      <span style="color:rgb(128,128,128);font-style:italic">// Before we complete the future we have to re-dispatch or we&#39;ll fall off the end of this method and Undertow<br></span><span style="color:rgb(128,128,128);font-style:italic">      // will complete the exchange on our behalf.<br></span><span style="color:rgb(128,128,128);font-style:italic">      </span>FormData data = exchange.getAttachment(FormDataParser.<span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">FORM_DATA</span>);<br>      <span style="color:rgb(0,0,128);font-weight:bold">if </span>(exchange.isInIoThread()) {<br>        <span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">log</span>.debug(<span style="color:rgb(0,128,0);font-weight:bold">&quot;Was on the IO thread. Re-dispatching.&quot;</span>);<br>        exchange.dispatch(SameThreadExecutor.<span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">INSTANCE</span>, () -&gt; afterDistpach(<span style="color:rgb(102,14,122)">data</span>));<br>      } <span style="color:rgb(0,0,128);font-weight:bold">else </span>{<br>        <span style="color:rgb(128,128,128);font-style:italic">// THIS THE MYSTERY LINE. WHY IS THIS NEEDED?<br></span><span style="color:rgb(128,128,128);font-style:italic">        </span>exchange.dispatch();<br>        afterDistpach(data);<br>      }<br>    }<br><br>    <span style="color:rgb(0,0,128);font-weight:bold">private void </span>afterDistpach(FormData data) {<br>      <span style="color:rgb(0,0,128);font-weight:bold">if </span>(data == <span style="color:rgb(0,0,128);font-weight:bold">null</span>) {<br>        <span style="color:rgb(102,14,122);font-weight:bold">toComplete</span>.completeExceptionally(<br>            <span style="color:rgb(0,0,128);font-weight:bold">new </span>UserVisibleException(<span style="color:rgb(0,128,0);font-weight:bold">&quot;Parsing of data failed.&quot;</span>, ResponseCodes.<span style="color:rgb(102,14,122);font-weight:bold;font-style:italic">BAD_REQUEST</span>));<br>      } <span style="color:rgb(0,0,128);font-weight:bold">else </span>{<br>        <span style="color:rgb(102,14,122);font-weight:bold">toComplete</span>.complete(data);<br>      }<br>    }<br>  }<br>}</pre></div></div><div dir="ltr">-- <br></div><div data-smartmail="gmail_signature"><div dir="ltr"><div style="font-size:small"><div style="font-size:13px;line-height:19.5px">CTO, Analytic Spot</div><div style="font-size:13px;line-height:19.5px">44 West Broadway #222</div><div style="font-size:13px;line-height:19.5px">Eugene, OR 97401<br></div><div style="font-size:13px;line-height:19.5px"><a href="http://analyticspot.com/" style="z-index: 0;">analyticspot.com</a> <span style="color:rgb(127,127,127);font-family:&#39;helvetica neue&#39;;font-size:11px;line-height:normal">• </span>425-296-6556</div></div><div style="font-size:small"><span style="line-height:19.5px"><a href="http://www.linkedin.com/in/oliverdain" style="z-index: 0;">www.linkedin.com/in/oliverdain</a></span></div></div></div>