Skip to content
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

Test PR for PR#155 #157

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions Snowflake.Data.Tests/DedicatedThreadSynchronisationContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using NUnit.Framework;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Snowflake.Data.Tests
{
/*
* This class will not deadlock, but it will cause tests to fail if the Send or Post methods have been called during a test
*/
public sealed class MockSynchronizationContext : SynchronizationContext
{
int callCount = 0;

public override void Post(SendOrPostCallback d, object state)
{
callCount++;
base.Post(d, state);
}

public override void Send(SendOrPostCallback d, object state)
{
callCount++;
base.Send(d, state);
}

public static void SetupContext()
{
SynchronizationContext.SetSynchronizationContext(new MockSynchronizationContext());
}

public static void Verify()
{
MockSynchronizationContext ctx = (MockSynchronizationContext)SynchronizationContext.Current;
Assert.Zero(ctx.callCount, "MockSynchronizationContext was called - this can cause deadlock. Make sure ConfigureAwait(false) is used in every await point in the library");
SynchronizationContext.SetSynchronizationContext(null);
}
}

/*
* This can be used to test what happens when a library metod is called from a SyncronizationContext.
* If there are any deadlocks in the code, this will trigger the deadlock.
*
* Usage:
* DedicatedThreadSynchronisationContext.RunInContext(_ => TestSimpleLargeResultSet());
*
*/
public sealed class DedicatedThreadSynchronisationContext : SynchronizationContext, IDisposable
{
public DedicatedThreadSynchronisationContext()
{
m_thread = new Thread(ThreadWorkerDelegate);
m_thread.Start(this);
}

public void Dispose()
{
m_queue.CompleteAdding();
}

/// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
/// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
/// <param name="state">The object passed to the delegate.</param>
public override void Post(SendOrPostCallback d, object state)
{
if (d == null) throw new ArgumentNullException("d");
m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
}

/// <summary> As
public override void Send(SendOrPostCallback d, object state)
{
using (var handledEvent = new ManualResetEvent(false))
{
Post(SendOrPostCallback_BlockingWrapper, Tuple.Create(d, state, handledEvent));
handledEvent.WaitOne();
}
}

public int WorkerThreadId { get { return m_thread.ManagedThreadId; } }

// This will run the callback in a synchronizationContext that is equivalent to a GUI or ASP.Net program
// If there are any async problems in the method, this code will provoke the deadlock.
public static void RunInContext(SendOrPostCallback d)
{
using (var ctx = new DedicatedThreadSynchronisationContext())
{
ctx.Send(d, null);
}
}

//=========================================================================================

private static void SendOrPostCallback_BlockingWrapper(object state)
{
var innerCallback = (state as Tuple<SendOrPostCallback, object, ManualResetEvent>);
try
{
innerCallback.Item1(innerCallback.Item2);
}
finally
{
innerCallback.Item3.Set();
}
}

/// <summary>The queue of work items.</summary>
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();

private readonly Thread m_thread = null;

/// <summary>Runs a loop to process all queued work items.</summary>
private void ThreadWorkerDelegate(object obj)
{
SynchronizationContext.SetSynchronizationContext(obj as SynchronizationContext);

try
{
foreach (var workItem in m_queue.GetConsumingEnumerable())
workItem.Key(workItem.Value);
}
catch (ObjectDisposedException) { }
}
}
}
31 changes: 26 additions & 5 deletions Snowflake.Data.Tests/SFBaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,33 @@ namespace Snowflake.Data.Tests
using NUnit.Framework.Interfaces;
using Newtonsoft.Json;

/*
* This is the base class for all tests that call blocking methods in the library - it uses MockSynchronizationContext to verify that
* there are no async deadlocks in the library
*
*/
[TestFixture]
public class SFBaseTest : SFBaseTestAsync
{
[SetUp]
public static void SetUpContext()
{
MockSynchronizationContext.SetupContext();
}

[TearDown]
public static void TearDownContext()
{
MockSynchronizationContext.Verify();
}
}

/*
* This is the base class for all tests that call async metodes in the library - it does not use a special SynchronizationContext
*
*/
[SetUpFixture]
public class SFBaseTest
public class SFBaseTestAsync
{
private const string connectionStringWithoutAuthFmt = "scheme={0};host={1};port={2};" +
"account={3};role={4};db={5};schema={6};warehouse={7}";
Expand Down Expand Up @@ -48,10 +73,6 @@ protected string ConnectionString

protected TestConfig testConfig { get; set; }

public SFBaseTest()
{
}

[OneTimeSetUp]
public void SFTestSetup()
{
Expand Down
Loading