From 0689bf4f311cf44d374701d2a493a2dee2b3bfbc Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 23 Nov 2016 15:26:41 +0100 Subject: [PATCH] U4-9201 - fix it all --- src/Umbraco.Core/DatabaseContext.cs | 60 ++++++++++--------- .../Persistence/DefaultDatabaseFactory.cs | 18 +++++- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- src/Umbraco.Web/Scheduling/LogScrubber.cs | 2 +- .../Scheduling/ScheduledPublishing.cs | 6 +- .../ServerRegistrationEventHandler.cs | 2 +- .../DataServices/UmbracoContentService.cs | 9 ++- 7 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index f8f31ee695..27013d8ecd 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -94,13 +94,13 @@ namespace Umbraco.Core public ISqlSyntaxProvider SqlSyntax { get; private set; } /// - /// Gets the object for doing CRUD operations against custom tables that resides in the Umbraco database. + /// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database. /// /// /// Should not be used for operation against standard Umbraco tables; as services should be used instead. /// Gets or creates an "ambient" database that is either stored in http context + available for the whole - /// request + auto-disposed at the end of the request, or in call context if there is no http context - in which - /// case it needs to be explicitely disposed so it is removed from call context. + /// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which + /// case it *must* be explicitely disposed (which will remove it from call context). /// public virtual UmbracoDatabase Database { @@ -108,24 +108,32 @@ namespace Umbraco.Core } /// - /// Replaces the "ambient" database (if any) and installs a new object. + /// Replaces the "ambient" database (if any) and by a new temp database. /// /// - /// The returned IDisposable *must* be diposed in order to properly disposed the safe database and + /// The returned IDisposable *must* be diposed in order to properly dispose the temp database and /// restore the original "ambient" database (if any). /// This is to be used in background tasks to ensure that they have an "ambient" database which /// will be properly removed from call context and does not interfere with anything else. In most case /// it is not replacing anything, just temporarily installing a database in context. /// - public virtual IDisposable UsingSafeDatabase + public virtual IDisposable UseSafeDatabase(bool force = false) { - get + var factory = _factory as DefaultDatabaseFactory; + if (factory == null) throw new NotSupportedException(); + if (DefaultDatabaseFactory.HasAmbientDatabase) { - var factory = _factory as DefaultDatabaseFactory; - if (factory == null) throw new NotSupportedException(); - var database = factory.CreateDatabaseInstance(DefaultDatabaseFactory.ContextOwner.None); - return new UsedDatabase(database, _logger); + // has ambient, + // if forcing, detach it and replace it with a new, temp, database + // when the UsingDatabase is disposed, the temp database is disposed and the original one is re-instated + // else do nothing (and nothing will be disposed) + return force + ? new UsingDatabase(DefaultDatabaseFactory.DetachAmbientDatabase(), factory.CreateDatabase()) + : new UsingDatabase(null, null); } + + // create a new, temp, database (will be disposed with UsingDatabase) + return new UsingDatabase(null, factory.CreateDatabase()); } /// @@ -788,30 +796,26 @@ namespace Umbraco.Core return true; } - private class UsedDatabase : IDisposable + private class UsingDatabase : IDisposable { - private readonly UmbracoDatabase _database; - private readonly ILogger _logger; + private readonly UmbracoDatabase _orig; + private readonly UmbracoDatabase _temp; - public UsedDatabase(UmbracoDatabase database, ILogger logger) + public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp) { - _logger = logger; - - // replace with ours - _database = DefaultDatabaseFactory.DetachDatabase(); - _logger.Debug("Detached db " + (_database == null ? "x" : _database.InstanceSid) + "."); - DefaultDatabaseFactory.AttachDatabase(database); - _logger.Debug("Attached db " + database.InstanceSid + "."); + _orig = orig; + _temp = temp; } public void Dispose() { - // restore the original DB - var db = DefaultDatabaseFactory.DetachDatabase(); - _logger.Debug("Detached db " + db.InstanceSid + "."); - db.Dispose(); - DefaultDatabaseFactory.AttachDatabase(_database); - _logger.Debug("Attached db " + (_database == null ? "x" : _database.InstanceSid) + "."); + if (_temp != null) + { + _temp.Dispose(); + if (_orig != null) + DefaultDatabaseFactory.AttachAmbientDatabase(_orig); + } + GC.SuppressFinalize(this); } } } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 5139da20d8..2ffee29490 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.Remoting.Messaging; using System.Web; using Umbraco.Core.Logging; @@ -250,12 +251,23 @@ namespace Umbraco.Core.Persistence static DefaultDatabaseFactory() { - SafeCallContext.Register(DetachDatabase, AttachDatabase); + SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase); } + // gets a value indicating whether there is an ambient database + internal static bool HasAmbientDatabase + { + get + { + return HttpContext.Current == null + ? NonContextValue != null + : HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null; + } + } + // detaches the current database // ie returns the database and remove it from whatever is "context" - internal static UmbracoDatabase DetachDatabase() + internal static UmbracoDatabase DetachAmbientDatabase() { UmbracoDatabase database; @@ -277,7 +289,7 @@ namespace Umbraco.Core.Persistence // attach a current database // ie assign it to whatever is "context" // throws if there already is a database - internal static void AttachDatabase(object o) + internal static void AttachAmbientDatabase(object o) { var database = o as UmbracoDatabase; if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o"); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6da7f4519b..24d889a06f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -21,7 +21,7 @@ full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;DEBUG_DATABASES prompt 4 false diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs index da49404b4d..7737101961 100644 --- a/src/Umbraco.Web/Scheduling/LogScrubber.cs +++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs @@ -78,7 +78,7 @@ namespace Umbraco.Web.Scheduling } // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete")) { Log.CleanLogs(GetLogScrubbingMaximumAge(_settings)); diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs index dc6c27959d..ba4fdddba5 100644 --- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs +++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs @@ -83,10 +83,8 @@ namespace Umbraco.Web.Scheduling }; // running on a background task, requires a safe database (see UsingSafeDatabase doc) - // - // this is because GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database - // - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + // (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) { //pass custom the authorization header request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext); diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 84b3e220ff..15ab8f244b 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -138,7 +138,7 @@ namespace Umbraco.Web.Strategies try { // running on a background task, requires a safe database (see UsingSafeDatabase doc) - using (ApplicationContext.Current.DatabaseContext.UsingSafeDatabase) + using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase(true)) { _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); } diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs index 4d2ea5bd9a..df0562f49c 100644 --- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs +++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs @@ -13,7 +13,6 @@ namespace UmbracoExamine.DataServices { public class UmbracoContentService : IContentService { - private readonly ApplicationContext _applicationContext; public UmbracoContentService() @@ -58,7 +57,7 @@ namespace UmbracoExamine.DataServices [Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")] public XDocument GetLatestContentByXPath(string xpath) { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one { var xmlContent = XDocument.Parse(""); var rootContent = _applicationContext.Services.ContentService.GetRootContent(); @@ -80,7 +79,7 @@ namespace UmbracoExamine.DataServices /// public bool IsProtected(int nodeId, string path) { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one { return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId)); } @@ -93,8 +92,8 @@ namespace UmbracoExamine.DataServices public IEnumerable GetAllUserPropertyNames() { - using (_applicationContext.DatabaseContext.UsingSafeDatabase) - { + using (_applicationContext.DatabaseContext.UseSafeDatabase()) // reuse current db if any else use & dispose one + { try { var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias");