Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix https://github.com/NuGet/WebBackgrounder/issues/7 - unhandled Except... #8

Closed
wants to merge 2 commits into from

2 participants

@TimLovellSmith

...ions from executing query in RunInTransaction (from miscellaneous failures such as SqlException) fail to restore _context to a valid state that allows further calls to RunInTransaction. (Fix #7)

@TimLovellSmith TimLovellSmith Fix #7 - unhandled Exceptions from executing query in RunInTransactio…
…n (from miscellaneous failures such as SqlException) fail to restore _context to a valid state that allows further calls to RunInTransaction.
681ae37
...kgrounder.EntityFramework/EntityWorkItemRepository.cs
@@ -23,13 +23,20 @@ public void RunInTransaction(Action query)
// For some reason, I get different behavior when I use this
// instead of _context.Database.Connection. This works, that doesn't. :(
((IObjectContextAdapter)_context).ObjectContext.Connection.Open();
- query();
- transaction.Complete();
+ try
+ {
+ query();
+ transaction.Complete();
+ transaction.Dispose();
+ }
+ finally
+ {
+ // REVIEW: Make sure this is really needed. I kept running into
+ // exceptions when I didn't do this, but I may be doing it wrong. -Phil 10/17/2011
+ _context.Dispose();
+ _context = _contextThunk();
@Haacked
Haacked added a note

This line makes no sense to me.

If you remove this line, then you can probably make _context declared as readonly. Then you don't need to do the whole local assignment thing we do in the Dispose method.

@Haacked
Haacked added a note

Nevermind. I see the point of this now. :package:

But it makes me think it's possible that _context could be null if the contextThunk returned null. So we should probably guard against that just to be safe, right?

I think it would be bad if _context ever returns null - it's a factory function, obviously if it fails it should throw, like a constructor would. Also we don't guard against _context being null anywhere except the Dispose() method currently. But the irony of that is even Dispose() doesn't set it to null.

@Haacked
Haacked added a note

I agree that _contextThunk should never return null. But thanks to the lack of support in the C# type system, there's no guarantee it won't. Yes, it'd be a programming error if that happened, but we should fail fast and make it clear that's why. So maybe on each call to it either Debug.Assert it's not null OR check the result of the call to _contextThunk and throw an exception if it's null.

Also, i noticed we forgot to check the arguments of the constructor. So it's possible that _contextThunk is null in the ctor resulting in a NullReferenceException. It'd be more helpful if that were an ArgumentNullException. BTW, I'm keenly aware that this is probably all my fault. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@TimLovellSmith TimLovellSmith +Unit tests, add null checking tests, ensure EntityWorkItemRepository…
…._context can never be assigned null, and simplify the Dispose() implementation to rely on transitivity for correctness.
91a0eb6
@TimLovellSmith

I've decided it's really weird that we also ensure the database connection is open as part of the transaction, so I've tried to shuffle it around a little, and simplify Dispose() given the new invariant that _context is never null.

@TimLovellSmith

Any more thoughts? :)

@TimLovellSmith

I now think that this might be the wrong fix. Because there's still a weird code path where
a) we dispose the old context
b) ensure context fails to get us a new context.
Withdrawing PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 22, 2013
  1. @TimLovellSmith

    Fix #7 - unhandled Exceptions from executing query in RunInTransactio…

    TimLovellSmith authored
    …n (from miscellaneous failures such as SqlException) fail to restore _context to a valid state that allows further calls to RunInTransaction.
  2. @TimLovellSmith

    +Unit tests, add null checking tests, ensure EntityWorkItemRepository…

    TimLovellSmith authored
    …._context can never be assigned null, and simplify the Dispose() implementation to rely on transitivity for correctness.
This page is out of date. Refresh to see the latest.
View
13 src/WebBackgrounder.DemoWeb/WebBackgrounder.DemoWeb.csproj
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -18,6 +19,11 @@
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<BuildPackage>false</BuildPackage>
+ <FileUpgradeFlags>
+ </FileUpgradeFlags>
+ <UpgradeBackupLocation>
+ </UpgradeBackupLocation>
+ <OldToolsVersion>4.0</OldToolsVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -132,8 +138,13 @@
<Content Include="Views\Shared\Error.cshtml" />
<Content Include="Views\Shared\_Layout.cshtml" />
</ItemGroup>
+ <PropertyGroup>
+ <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
+ <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+ </PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
- <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
+ <Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
View
57 src/WebBackgrounder.EntityFramework/EntityWorkItemRepository.cs
@@ -1,5 +1,6 @@
using System;
using System.Data.Entity.Infrastructure;
+using System.Diagnostics;
using System.Linq;
using System.Transactions;
@@ -12,24 +13,36 @@ public class EntityWorkItemRepository : IWorkItemRepository, IDisposable
public EntityWorkItemRepository(Func<IWorkItemsContext> contextThunk)
{
+ if (contextThunk == null)
+ {
+ throw new ArgumentNullException("contextThunk");
+ }
+
_contextThunk = contextThunk;
- _context = _contextThunk();
+ EnsureContext();
}
public void RunInTransaction(Action query)
{
- using (var transaction = new TransactionScope())
+ // For some reason, I get different behavior when I use this
+ // instead of _context.Database.Connection. This works, that doesn't. :(
+ ((IObjectContextAdapter)_context).ObjectContext.Connection.Open();
+
+ try
{
- // For some reason, I get different behavior when I use this
- // instead of _context.Database.Connection. This works, that doesn't. :(
- ((IObjectContextAdapter)_context).ObjectContext.Connection.Open();
- query();
- transaction.Complete();
+ using (var transaction = new TransactionScope())
+ {
+ query();
+ transaction.Complete();
+ }
+ }
+ finally
+ {
+ // REVIEW: Make sure this is really needed. I kept running into
+ // exceptions when I didn't do this, but I may be doing it wrong. -Phil 10/17/2011
+ _context.Dispose();
+ EnsureContext();
}
- // REVIEW: Make sure this is really needed. I kept running into
- // exceptions when I didn't do this, but I may be doing it wrong. -Phil 10/17/2011
- _context.Dispose();
- _context = _contextThunk();
}
public IWorkItem GetLastWorkItem(IJob job)
@@ -37,7 +50,7 @@ public IWorkItem GetLastWorkItem(IJob job)
return (from w in _context.WorkItems
where w.JobName == job.Name
orderby w.Started descending
- select w).FirstOrDefault() as IWorkItem;
+ select w).FirstOrDefault();
}
public long CreateWorkItem(string workerId, IJob job)
@@ -69,18 +82,26 @@ public void SetWorkItemFailed(long workItemId, Exception exception)
_context.SaveChanges();
}
- private WorkItem GetWorkItem(long workerId)
+ public void Dispose()
{
- return _context.WorkItems.Find(workerId);
+ Debug.Assert(_context != null);
+ _context.Dispose();
}
- public void Dispose()
+ private void EnsureContext()
{
- var context = _context;
- if (context != null)
+ var tempContext = _contextThunk();
+ if (tempContext == null)
{
- context.Dispose();
+ throw new InvalidOperationException("Context thunk must never return null");
}
+
+ _context = tempContext;
+ }
+
+ private WorkItem GetWorkItem(long workerId)
+ {
+ return _context.WorkItems.Find(workerId);
}
}
}
View
15 src/WebBackgrounder.UnitTests/EntityWorkItemRepositoryFacts.cs
@@ -8,6 +8,21 @@ namespace WebBackgrounder.UnitTests
{
public class EntityWorkItemRepositoryFacts
{
+ public class TheConstructor
+ {
+ [Fact]
+ public void ThrowsForANullFactoryThunk()
+ {
+ Assert.Throws<ArgumentNullException>(() => new EntityWorkItemRepository(null));
+ }
+
+ [Fact]
+ public void ThrowsForAnInvalidFactoryThunk()
+ {
+ Assert.Throws<InvalidOperationException>(() => new EntityWorkItemRepository(() => null));
+ }
+ }
+
public class TheGetLastWorkItemMethod
{
[Fact]
View
43 src/WebBackgrounder.VS2012.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{3F870BEB-5F40-4CC3-9226-2FBE7A930E81}"
+ ProjectSection(SolutionItems) = preProject
+ ..\README.md = ..\README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.DemoWeb", "WebBackgrounder.DemoWeb\WebBackgrounder.DemoWeb.csproj", "{0A004B9F-4440-4C05-9D84-E38726D6162F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder", "WebBackgrounder\WebBackgrounder.csproj", "{0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.UnitTests", "WebBackgrounder.UnitTests\WebBackgrounder.UnitTests.csproj", "{6F6BBD68-74D1-4C32-AA9C-42B8690BA484}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebBackgrounder.EntityFramework", "WebBackgrounder.EntityFramework\WebBackgrounder.EntityFramework.csproj", "{06D8DE5D-F101-4CD5-B406-8A211216FCE1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0A004B9F-4440-4C05-9D84-E38726D6162F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0A004B9F-4440-4C05-9D84-E38726D6162F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0A004B9F-4440-4C05-9D84-E38726D6162F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0A004B9F-4440-4C05-9D84-E38726D6162F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0AD3BB44-E2FA-4A10-A44F-7CABA7FACF4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F6BBD68-74D1-4C32-AA9C-42B8690BA484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {06D8DE5D-F101-4CD5-B406-8A211216FCE1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
Something went wrong with that request. Please try again.