Wednesday 1 July 2015

Aspnet autoconfig

The defaults of max connection allowed in an asp.net hosted process with machine config autoconfig="true" appears to have changed post .net 4.

Max connections can be set using the system.net max connections element 

  <system.net>
    <connectionManagement>
      <add address="*" maxconnection="50"/>
    </connectionManagement>
  </system.net>


or through System.Net.ServicePointManager.DefaultConnectionLimit in code 

Changes are honored ONLY once autoconfig has been turned OFF 

With autoconfig="true" the max connections now appears to default to max.int and no longer limited to 12*number-of-cores which was the original default.


Task exceptions

The following code demonstrates that you have to think carefully where exceptions that happen during task execution get handled, it may not always be where you think and this can lead to unexpected behavior on the "sad code execution path" (which infrequently gets enough attention) or swallowed / unobserved exceptions, or both.


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest2
    {
        [TestMethod]
        public async Task TestWork() 
        {
            TaskScheduler.UnobservedTaskException += (object sender, 
                UnobservedTaskExceptionEventArgs e) =>  {
                // not fired, as escalation policy doesn't kick in
                Assert.IsNotNull(e.Exception); 
            };
            var errorOccured = false;
            var work = Important.Work(async () => {
                // extraWork lambda
                await Task.Delay(100);
                throw new Exception("oops");
            }, () => {
                errorOccured = true;// onException func not hit
            })
            .ContinueWith(t => {
                // otherwise ex swallowed when continuation 
                // exists use only-on-faulted option
                Assert.IsTrue(t.IsFaulted);
                //t.Wait(); // observed exception on wait
            });
            //work.Wait(); // exception would NOT be observed here
            await work; // exception thrown here if NO continuation exists
            Assert.IsFalse(errorOccured);
        }
        [TestMethod]
        public async Task TestBetter()
        {
            TaskScheduler.UnobservedTaskException += (object sender, 
                UnobservedTaskExceptionEventArgs e) =>{
                // not fired, as escalation policy doesn't kick in
                Assert.IsNotNull(e.Exception); 
            };
            var errorOccured = false;
            var work = Important.BetterWork(async () =>{
                // extraWork lambda
                await Task.Delay(100);
                throw new Exception("oops");
            }, () =>{
                // on onException func hit before RETHROW in BetterWork()
                errorOccured = true; 
            })
            .ContinueWith(t =>{
                Assert.IsTrue(t.IsFaulted);
                //t.Wait(); // RETHROWN ex observed on wait 
                // otherwise RETHROWN exception swallowed
                // use only-on-faulted option
            });
            //work.Wait(); // RETHROWN exception would be observed here
            // RETHROWN exception caught here if NO continuation exists
            await work; 
            Assert.IsTrue(errorOccured);
        }

        public static class Important
        {
            public static Task Work(Func<Task> extraWork, 
                 Action onException)
            {
                //
                // work
                //
                try
                {
                    return extraWork();
                }
                catch (Exception)
                {
                    onException(); // not hit
                    throw;
                }
            }
            public static async Task BetterWork(Func<Task> extraWork, 
                 Action onException)
            {
                //
                // work
                //
                try
                {
                    await extraWork();
                }
                catch (Exception)
                {
                    onException(); // might not always be hit
                    throw;
                }
            }
        }

    }
}