2015-03-04 12:16:28 +01:00
using System ;
using System.Collections.Generic ;
2015-07-15 17:27:01 +02:00
using System.Linq ;
2015-03-04 12:16:28 +01:00
using System.Web ;
2015-07-15 17:27:01 +02:00
using Newtonsoft.Json ;
using umbraco.interfaces ;
2015-03-04 12:16:28 +01:00
using Umbraco.Core ;
2015-07-15 17:27:01 +02:00
using Umbraco.Core.Models.Rdbms ;
2015-03-04 12:16:28 +01:00
using Umbraco.Core.Sync ;
using Umbraco.Web.Routing ;
2015-07-29 15:22:22 +02:00
using Umbraco.Core.Logging ;
2017-10-31 15:11:45 +11:00
using Umbraco.Web.Scheduling ;
2015-03-04 12:16:28 +01:00
namespace Umbraco.Web
{
2015-03-27 10:25:25 +11:00
/// <summary>
/// An <see cref="IServerMessenger"/> implementation that works by storing messages in the database.
/// </summary>
/// <remarks>
/// This binds to appropriate umbraco events in order to trigger the Boot(), Sync() & FlushBatch() calls
/// </remarks>
2015-07-24 17:34:06 +02:00
public class BatchedDatabaseServerMessenger : DatabaseServerMessenger
2015-03-04 12:16:28 +01:00
{
public BatchedDatabaseServerMessenger ( ApplicationContext appContext , bool enableDistCalls , DatabaseServerMessengerOptions options )
2015-03-27 10:16:49 +11:00
: base ( appContext , enableDistCalls , options )
2017-10-31 15:11:45 +11:00
{
Scheduler . Initializing + = Scheduler_Initializing ;
}
/// <summary>
/// Occurs when the scheduler initializes all scheduling activity when the app is ready
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Scheduler_Initializing ( object sender , List < IBackgroundTask > e )
{
//if the current resolver is 'this' then we will start the scheduling
var isMessenger = ServerMessengerResolver . HasCurrent & & ReferenceEquals ( ServerMessengerResolver . Current . Messenger , this ) ;
if ( isMessenger )
{
//start the background task runner for processing instructions
const int delayMilliseconds = 60000 ;
var instructionProcessingRunner = new BackgroundTaskRunner < IBackgroundTask > ( "InstructionProcessing" , ApplicationContext . ProfilingLogger . Logger ) ;
var instructionProcessingTask = new InstructionProcessing ( instructionProcessingRunner , this , delayMilliseconds , Options . ThrottleSeconds * 1000 ) ;
instructionProcessingRunner . TryAdd ( instructionProcessingTask ) ;
e . Add ( instructionProcessingTask ) ;
}
}
2015-07-29 11:22:12 +02:00
// invoked by BatchedDatabaseServerMessengerStartup which is an ApplicationEventHandler
// with default "ShouldExecute", so that method will run if app IsConfigured and database
// context IsDatabaseConfigured - we still want to check CanConnect though to be safe
internal void Startup ( )
{
UmbracoModule . EndRequest + = UmbracoModule_EndRequest ;
if ( ApplicationContext . DatabaseContext . CanConnect = = false )
{
ApplicationContext . ProfilingLogger . Logger . Warn < BatchedDatabaseServerMessenger > (
"Cannot connect to the database, distributed calls will not be enabled for this server." ) ;
}
else
{
Boot ( ) ;
}
2015-07-23 12:28:17 +02:00
}
2017-10-31 15:11:45 +11:00
/// <summary>
/// This will process cache instructions on a background thread and will run every 5 seconds (or whatever is defined in the <see cref="DatabaseServerMessengerOptions.ThrottleSeconds"/>)
/// </summary>
private class InstructionProcessing : RecurringTaskBase
2015-03-04 12:16:28 +01:00
{
2017-10-31 15:11:45 +11:00
private readonly DatabaseServerMessenger _messenger ;
public InstructionProcessing ( IBackgroundTaskRunner < RecurringTaskBase > runner ,
DatabaseServerMessenger messenger ,
int delayMilliseconds , int periodMilliseconds )
: base ( runner , delayMilliseconds , periodMilliseconds )
{
_messenger = messenger ;
}
public override bool PerformRun ( )
{
_messenger . Sync ( ) ;
//return true to repeat
return true ;
}
public override bool IsAsync
2015-03-04 12:16:28 +01:00
{
2017-10-31 15:11:45 +11:00
get { return false ; }
2015-03-04 12:16:28 +01:00
}
}
2016-11-18 13:48:18 +01:00
private void UmbracoModule_EndRequest ( object sender , UmbracoRequestEventArgs e )
2015-03-04 12:16:28 +01:00
{
// will clear the batch - will remain in HttpContext though - that's ok
FlushBatch ( ) ;
}
2015-07-15 17:27:01 +02:00
protected override void DeliverRemote ( IEnumerable < IServerAddress > servers , ICacheRefresher refresher , MessageType messageType , IEnumerable < object > ids = null , string json = null )
{
var idsA = ids = = null ? null : ids . ToArray ( ) ;
Type arrayType ;
if ( GetArrayType ( idsA , out arrayType ) = = false )
throw new ArgumentException ( "All items must be of the same type, either int or Guid." , "ids" ) ;
BatchMessage ( servers , refresher , messageType , idsA , arrayType , json ) ;
}
public void FlushBatch ( )
{
var batch = GetBatch ( false ) ;
if ( batch = = null ) return ;
var instructions = batch . SelectMany ( x = > x . Instructions ) . ToArray ( ) ;
batch . Clear ( ) ;
2016-10-25 16:01:55 +02:00
//Write the instructions but only create JSON blobs with a max instruction count equal to MaxProcessingInstructionCount
foreach ( var instructionsBatch in instructions . InGroupsOf ( Options . MaxProcessingInstructionCount ) )
{
WriteInstructions ( instructionsBatch ) ;
}
2015-09-22 15:38:19 +02:00
}
2015-07-15 17:27:01 +02:00
2016-10-25 16:01:55 +02:00
private void WriteInstructions ( IEnumerable < RefreshInstruction > instructions )
2015-09-22 15:38:19 +02:00
{
2015-07-15 17:27:01 +02:00
var dto = new CacheInstructionDto
{
UtcStamp = DateTime . UtcNow ,
Instructions = JsonConvert . SerializeObject ( instructions , Formatting . None ) ,
OriginIdentity = LocalIdentity
} ;
ApplicationContext . DatabaseContext . Database . Insert ( dto ) ;
}
2015-09-22 15:38:19 +02:00
protected ICollection < RefreshInstructionEnvelope > GetBatch ( bool create )
2015-03-04 12:16:28 +01:00
{
2015-09-22 15:38:19 +02:00
// try get the http context from the UmbracoContext, we do this because in the case we are launching an async
2015-09-22 14:39:55 +02:00
// thread and we know that the cache refreshers will execute, we will ensure the UmbracoContext and therefore we
// can get the http context from it
var httpContext = ( UmbracoContext . Current = = null ? null : UmbracoContext . Current . HttpContext )
2015-09-22 15:38:19 +02:00
// if this is null, it could be that an async thread is calling this method that we weren't aware of and the UmbracoContext
2015-09-22 14:39:55 +02:00
// wasn't ensured at the beginning of the thread. We can try to see if the HttpContext.Current is available which might be
// the case if the asp.net synchronization context has kicked in
? ? ( HttpContext . Current = = null ? null : new HttpContextWrapper ( HttpContext . Current ) ) ;
2015-09-22 15:38:19 +02:00
// if no context was found, return null - we cannot not batch
if ( httpContext = = null ) return null ;
2015-03-04 12:16:28 +01:00
var key = typeof ( BatchedDatabaseServerMessenger ) . Name ;
// no thread-safety here because it'll run in only 1 thread (request) at a time
var batch = ( ICollection < RefreshInstructionEnvelope > ) httpContext . Items [ key ] ;
2015-09-22 15:38:19 +02:00
if ( batch = = null & & create )
2015-03-04 12:16:28 +01:00
httpContext . Items [ key ] = batch = new List < RefreshInstructionEnvelope > ( ) ;
return batch ;
}
2015-07-15 17:27:01 +02:00
protected void BatchMessage (
IEnumerable < IServerAddress > servers ,
ICacheRefresher refresher ,
MessageType messageType ,
IEnumerable < object > ids = null ,
Type idType = null ,
string json = null )
{
var batch = GetBatch ( true ) ;
2015-09-22 15:38:19 +02:00
var instructions = RefreshInstruction . GetInstructions ( refresher , messageType , ids , idType , json ) ;
2015-07-15 17:27:01 +02:00
2015-09-22 15:38:19 +02:00
// batch if we can, else write to DB immediately
if ( batch = = null )
2016-10-25 16:01:55 +02:00
{
//only write the json blob with a maximum count of the MaxProcessingInstructionCount
foreach ( var maxBatch in instructions . InGroupsOf ( Options . MaxProcessingInstructionCount ) )
{
WriteInstructions ( maxBatch ) ;
}
}
2015-09-22 15:38:19 +02:00
else
2016-10-25 16:01:55 +02:00
{
2015-09-22 15:38:19 +02:00
batch . Add ( new RefreshInstructionEnvelope ( servers , refresher , instructions ) ) ;
2016-10-25 16:01:55 +02:00
}
2015-07-29 11:22:12 +02:00
}
2015-03-04 12:16:28 +01:00
}
}