{"id":5531,"date":"2018-09-30T13:00:25","date_gmt":"2018-09-30T13:00:25","guid":{"rendered":"http:\/\/writeasync.net\/?p=5531"},"modified":"2018-09-29T18:01:33","modified_gmt":"2018-09-29T18:01:33","slug":"find-the-issue-long-requests","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5531","title":{"rendered":"Find the issue: long requests"},"content":{"rendered":"<p><a href=\"http:\/\/writeasync.net\/?p=5529\">In the previous post<\/a>, I discussed a few design changes to a sample Web API controller to fix up the parameters. Here is where we ended up:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic sealed class MyCommandController : ApiController\r\n{\r\n    public string Post(&#x5B;FromUri] MyCommandParameters p)\r\n    {\r\n        Process myCommand = Process.Start(\r\n            &quot;MyCommand.exe&quot;,\r\n            $&quot;-useTheForce {p.useTheForce} -timeout {p.timeout} -auditMessage {p.auditMessage}&quot;);\r\n        myCommand.WaitForExit();\r\n        return $&quot;Completed with exit code {myCommand.ExitCode}&quot;;\r\n    }\r\n}\r\n<\/pre>\n<p>Of course, we&#8217;re not done yet. I ask again: can you find the next issue in this ASP.NET Web API code snippet?<\/p>\n<p>Today I would like to direct your attention to that <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.diagnostics.process.waitforexit?view=netframework-4.7.2\">WaitForExit<\/a> call. This is a blocking call which unnecessarily ties up the request thread. You might think, &#8220;No problem, let&#8217;s just use an async controller action instead!&#8221;<\/p>\n<p>Unfortunately, you will immediately run into another problem once you go that route: there is no async version of <code>WaitForExit<\/code>. All is not lost, however; you could use the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.diagnostics.process.exited?view=netframework-4.7.2\">Exited<\/a> event to <a href=\"http:\/\/writeasync.net\/?p=1251\">cobble together an async Process pattern<\/a>. It would be a bit of work but certainly achievable. Still, you would have a more fundamental problem &#8212; how long are you actually willing to tie up the HTTP request waiting for the process to exit?<\/p>\n<p>In the general case, it is hard to predict how long an external action may take to complete. Even if usually takes just a few seconds to execute the command, there will always be outliers (as <a href=\"https:\/\/blogs.technet.microsoft.com\/markrussinovich\/2008\/09\/22\/the-case-of-the-slooooow-system\/\">Mark Russinovich can attest<\/a>). We can also use the clue that the command itself in this example takes in a timeout parameter measured in <em>seconds<\/em>. It is safe to say that the request here would commonly need to wait quite a while, relatively speaking.<\/p>\n<p>Not to worry, though. This is what <a href=\"https:\/\/tools.ietf.org\/html\/rfc7231#section-6.3.3\">HTTP 202<\/a> was made for:<\/p>\n<blockquote><p>The 202 response is intentionally noncommittal. Its purpose is to allow a server to accept a request for some other process [&#8230;] without requiring that the user agent&#8217;s connection to the server persist until the process is completed.<\/p><\/blockquote>\n<p>So what we would want to do here is return an initial 202 response with some basic status tracking information and allow the user to call back later for the result. This same technique is used by the <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/azure-resource-manager\/resource-group-overview\">Azure Resource Manager<\/a> for its <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/azure-resource-manager\/resource-manager-async-operations\">asynchronous REST API<\/a>.<\/p>\n<p>For this example, we will use a design where all the work is delegated to a <code>MyCommands<\/code> class. It will start new commands with a <code>Start<\/code> method and track existing commands with a <code>Get<\/code> method. The controller actions will be modified accordingly; the <code>Post<\/code> method will call <code>Start<\/code> and return a tracking ID right away, while the new <code>Get<\/code> method will return the status based on ID. The basic skeleton now looks like this:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic sealed class MyCommandController : ApiController\r\n{\r\n    private readonly MyCommands commands;\r\n\r\n    public MyCommandController(MyCommands commands)\r\n    {\r\n        this.commands = commands;\r\n    }\r\n\r\n    public HttpResponseMessage Post(&#x5B;FromUri] MyCommandParameters p)\r\n    {\r\n        return this.Request.CreateResponse(HttpStatusCode.Accepted, this.commands.Start(p));\r\n    }\r\n\r\n    public MyCommandResult Get(string id)\r\n    {\r\n        return this.commands.Get(id);\r\n    }\r\n}\r\n<\/pre>\n<p>Note the use of a new data type <code>MyCommandResult<\/code> to wrap the original command completion message. This is so we can allow programmatic detection of the pending\/finished state to aid in client polling.<\/p>\n<p>To make the routes look nice, we would want the experience of calling <code>POST<\/code> on <code>api\/MyCommand\/<\/code> and <code>GET<\/code> on <code>api\/MyCommand\/<strong>id<\/strong><\/code>. This is relatively easy in Web API by <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/web-api\/overview\/web-api-routing-and-actions\/attribute-routing-in-web-api-2#enabling-attribute-routing\">using convention-based routing<\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n            config.Routes.MapHttpRoute(\r\n                name: &quot;DefaultApi&quot;,\r\n                routeTemplate: &quot;api\/{controller}\/{id}&quot;,\r\n                defaults: new { id = RouteParameter.Optional });\r\n<\/pre>\n<p>Let&#8217;s create the <code>MyCommands<\/code> class next. This is where all the <code>Process<\/code> functionality will now live:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic sealed class MyCommands\r\n{\r\n    private readonly ConcurrentDictionary&lt;string, Process&gt; commands;\r\n\r\n    public MyCommands()\r\n    {\r\n        this.commands = new ConcurrentDictionary&lt;string, Process&gt;();\r\n    }\r\n\r\n    public string Start(MyCommandParameters p)\r\n    {\r\n        Process myCommand = Process.Start(\r\n            &quot;MyCommand.exe&quot;,\r\n            $&quot;-useTheForce {p.useTheForce} -timeout {p.timeout} -auditMessage {p.auditMessage}&quot;);\r\n        string id = Guid.NewGuid().ToString();\r\n        this.commands&#x5B;id] = command;\r\n        return id;\r\n    }\r\n\r\n    public MyCommandResult Get(string id)\r\n    {\r\n        Process command;\r\n        if (!this.commands.TryGetValue(id, out command))\r\n        {\r\n            return MyCommandResult.NotFound(id);\r\n        }\r\n\r\n        if (!command.HasExited)\r\n        {\r\n            return MyCommandResult.Pending(id);\r\n        }\r\n\r\n        return MyCommandResult.Completed(id, command.ExitCode);\r\n    }\r\n}\r\n<\/pre>\n<p>We are using a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.collections.concurrent.concurrentdictionary-2?view=netframework-4.7.2\">ConcurrentDictionary<\/a> to store the ID to Process mapping, since many parallel requests could come in and we do not want to corrupt our state. The results are generated using MyCommandResult via the <a href=\"http:\/\/www.cs.technion.ac.il\/users\/yechiel\/c++-faq\/named-ctor-idiom.html\">named constructor idiom<\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic sealed class MyCommandResult\r\n{\r\n    private MyCommandResult(string id, MyCommandStatus status, string detail)\r\n    {\r\n        this.Id = id;\r\n        this.Status = status;\r\n        this.Detail = detail;\r\n    }\r\n\r\n    public string Id { get; }\r\n\r\n    public MyCommandStatus Status { get; }\r\n\r\n    public string Detail { get; }\r\n\r\n    public static MyCommandResult NotFound(string id)\r\n    {\r\n        return new MyCommandResult(id, MyCommandStatus.NotFound, $&quot;The command {id} was not found.&quot;);\r\n    }\r\n\r\n    public static MyCommandResult Pending(string id)\r\n    {\r\n        return new MyCommandResult(id, MyCommandStatus.Pending, $&quot;The command {id} is still running.&quot;);\r\n    }\r\n\r\n    public static MyCommandResult Completed(string id, int exitCode)\r\n    {\r\n        return new MyCommandResult(id, MyCommandStatus.Completed, $&quot;The command {id} completed with exit code {exitCode}.&quot;);\r\n    }\r\n}\r\n<\/pre>\n<p>Finally, the simple status enum for this <a href=\"https:\/\/en.wikipedia.org\/wiki\/Three-valued_logic\">three-value model<\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic enum MyCommandStatus\r\n{\r\n    NotFound,\r\n    Pending,\r\n    Completed\r\n}\r\n<\/pre>\n<p>Admittedly, we have added dozens more lines of code using this approach. But the payoff is improved concurrency, efficiency, and flexibility. In any case, the extra code is certainly not complex &#8212; one might even say it is <a href=\"https:\/\/github.com\/matthiasn\/talk-transcripts\/blob\/master\/Hickey_Rich\/SimpleMadeEasy.md\">simple to read and write<\/a>.<\/p>\n<p>So, are we done? Not yet, I&#8217;m afraid. Since our controller no longer has a public parameterless constructor, <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/web-api\/overview\/advanced\/dependency-injection#the-web-api-dependency-resolver\">the default resolver<\/a> will not be able to instantiate it for us anymore. Rather than go to a full blown <a href=\"https:\/\/www.hanselman.com\/blog\/ListOfNETDependencyInjectionContainersIOC.aspx\">DI container<\/a> like <a href=\"https:\/\/github.com\/unitycontainer\/unity\">Unity<\/a>, I generally prefer to hand-code a simple <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/web-api\/overview\/web-api-routing-and-actions\/routing-and-action-selection#extension-points\">HTTP controller activator<\/a> such as in <a href=\"http:\/\/blog.ploeh.dk\/2014\/06\/03\/compile-time-lifetime-matching\/\">Mark Seemann&#8217;s example<\/a>. For this situation, the following will suffice:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing System;\r\nusing System.Net.Http;\r\nusing System.Web.Http.Controllers;\r\nusing System.Web.Http.Dispatcher;\r\n\r\ninternal sealed class MyActivator : IHttpControllerActivator\r\n{\r\n    private readonly MyCommands commands;\r\n\r\n    public MyActivator(MyCommands commands)\r\n    {\r\n        this.commands = commands;\r\n    }\r\n\r\n    public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n    {\r\n        if (controllerType == typeof(MyCommandController))\r\n        {\r\n            return new MyCommandController(this.commands);\r\n        }\r\n\r\n        return null;\r\n    }\r\n}\r\n<\/pre>\n<p>To register the activator, you need to add one more line to your <code>Startup.Configuration<br \/>\n<\/code> method:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nconfig.Services.Replace(typeof(IHttpControllerActivator), new MyActivator(new MyCommands()));\r\n<\/pre>\n<p>Now we have a fully functional controller with proper async operation support! If you <code>POST<\/code>, you should quickly get back a response like so:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nHTTP\/1.1 202 Accepted\r\nContent-Length: 38\r\nContent-Type: application\/json; charset=utf-8\r\nDate: Sat, 29 Sep 2018 17:52:03 GMT\r\nServer: Microsoft-HTTPAPI\/2.0\r\n\r\n&quot;dea4ce75-baed-4232-b6be-cc076aceef55&quot;\r\n<\/pre>\n<p>Given the ID, you can <code>GET<\/code> the status of the operation:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n\/\/ When still running...\r\n{\r\n  &quot;Id&quot;: &quot;dea4ce75-baed-4232-b6be-cc076aceef55&quot;,\r\n  &quot;Status&quot;: 1,\r\n  &quot;Detail&quot;: &quot;The command dea4ce75-baed-4232-b6be-cc076aceef55 is still running.&quot;\r\n}\r\n\r\n\/\/ When completed...\r\n{\r\n  &quot;Id&quot;: &quot;dea4ce75-baed-4232-b6be-cc076aceef55&quot;,\r\n  &quot;Status&quot;: 2,\r\n  &quot;Detail&quot;: &quot;The command dea4ce75-baed-4232-b6be-cc076aceef55 completed with exit code 0.&quot;\r\n}\r\n\r\n\/\/ When not found...\r\n{\r\n  &quot;Id&quot;: &quot;abcd&quot;,\r\n  &quot;Status&quot;: 0,\r\n  &quot;Detail&quot;: &quot;The command abcd was not found.&quot;\r\n}\r\n<\/pre>\n<p>It functions properly, and yet there are still a few issues here. But we will dig into those next time&#8230;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous post, I discussed a few design changes to a sample Web API controller to fix up the parameters. Here is where we ended up: public sealed class MyCommandController : ApiController { public string Post(&#x5B;FromUri] MyCommandParameters p) {&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21,91],"tags":[],"class_list":["post-5531","post","type-post","status-publish","format-standard","hentry","category-async","category-design"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5531","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5531"}],"version-history":[{"count":3,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5531\/revisions"}],"predecessor-version":[{"id":5534,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5531\/revisions\/5534"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5531"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5531"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5531"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}