Skip to content

Commit

Permalink
Adding internal filewatch logic to efficiently reload functions when …
Browse files Browse the repository at this point in the history
…changed
  • Loading branch information
mathewc committed Dec 18, 2015
1 parent e0a783e commit 7b702c6
Show file tree
Hide file tree
Showing 19 changed files with 260 additions and 99 deletions.
10 changes: 8 additions & 2 deletions src/WebJobs.Script.Host/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ static void Main(string[] args)
RootPath = rootPath
};

ScriptHost host = ScriptHost.Create(config);
host.RunAndBlock();
// Start the host and restart it if requested
ScriptHost host = null;
do
{
host = ScriptHost.Create(config);
host.RunAndBlock();
}
while (host.Restart);
}
}
}
10 changes: 5 additions & 5 deletions src/WebJobs.Script/Binding/Binding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ namespace Microsoft.Azure.WebJobs.Script
{
internal abstract class Binding
{
private readonly JobHostConfiguration _config;
private readonly ScriptHostConfiguration _config;

protected Binding(JobHostConfiguration config, string name, string type, FileAccess fileAccess, bool isTrigger)
protected Binding(ScriptHostConfiguration config, string name, string type, FileAccess fileAccess, bool isTrigger)
{
_config = config;
Name = name;
Expand All @@ -37,7 +37,7 @@ protected Binding(JobHostConfiguration config, string name, string type, FileAcc

public abstract Task BindAsync(IBinder binder, Stream stream, IReadOnlyDictionary<string, string> bindingData);

internal static Collection<Binding> GetBindings(JobHostConfiguration config, JArray bindingArray, FileAccess fileAccess)
internal static Collection<Binding> GetBindings(ScriptHostConfiguration config, JArray bindingArray, FileAccess fileAccess)
{
Collection<Binding> bindings = new Collection<Binding>();

Expand Down Expand Up @@ -103,12 +103,12 @@ internal static Collection<Binding> GetBindings(JobHostConfiguration config, JAr

protected string Resolve(string name)
{
if (_config.NameResolver == null)
if (_config.HostConfig.NameResolver == null)
{
return name;
}

return _config.NameResolver.ResolveWholeString(name);
return _config.HostConfig.NameResolver.ResolveWholeString(name);
}
}
}
2 changes: 1 addition & 1 deletion src/WebJobs.Script/Binding/BlobBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class BlobBinding : Binding
{
private readonly BindingTemplate _pathBindingTemplate;

public BlobBinding(JobHostConfiguration config, string name, string path, FileAccess fileAccess, bool isTrigger) : base(config, name, "blob", fileAccess, isTrigger)
public BlobBinding(ScriptHostConfiguration config, string name, string path, FileAccess fileAccess, bool isTrigger) : base(config, name, "blob", fileAccess, isTrigger)
{
Path = path;
_pathBindingTemplate = BindingTemplate.FromString(Path);
Expand Down
2 changes: 1 addition & 1 deletion src/WebJobs.Script/Binding/QueueBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class QueueBinding : Binding
{
private readonly BindingTemplate _queueNameBindingTemplate;

public QueueBinding(JobHostConfiguration config, string name, string queueName, FileAccess fileAccess, bool isTrigger) : base(config, name, "queue", fileAccess, isTrigger)
public QueueBinding(ScriptHostConfiguration config, string name, string queueName, FileAccess fileAccess, bool isTrigger) : base(config, name, "queue", fileAccess, isTrigger)
{
QueueName = queueName;
_queueNameBindingTemplate = BindingTemplate.FromString(QueueName);
Expand Down
2 changes: 1 addition & 1 deletion src/WebJobs.Script/Binding/ServiceBusBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class ServiceBusBinding : Binding
{
private readonly BindingTemplate _queueOrTopicNameBindingTemplate;

public ServiceBusBinding(JobHostConfiguration config, string name, string queueOrTopicName, FileAccess fileAccess, bool isTrigger) : base(config, name, "serviceBus", fileAccess, isTrigger)
public ServiceBusBinding(ScriptHostConfiguration config, string name, string queueOrTopicName, FileAccess fileAccess, bool isTrigger) : base(config, name, "serviceBus", fileAccess, isTrigger)
{
QueueOrTopicName = queueOrTopicName;
_queueOrTopicNameBindingTemplate = BindingTemplate.FromString(QueueOrTopicName);
Expand Down
2 changes: 1 addition & 1 deletion src/WebJobs.Script/Binding/TableBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal class TableBinding : Binding
private readonly BindingTemplate _rowKeyBindingTemplate;
private readonly TableQuery _tableQuery;

public TableBinding(JobHostConfiguration config, string name, string tableName, string partitionKey, string rowKey, FileAccess fileAccess, TableQuery tableQuery = null) : base(config, name, "queue", fileAccess, false)
public TableBinding(ScriptHostConfiguration config, string name, string tableName, string partitionKey, string rowKey, FileAccess fileAccess, TableQuery tableQuery = null) : base(config, name, "queue", fileAccess, false)
{
TableName = tableName;
PartitionKey = partitionKey;
Expand Down
114 changes: 85 additions & 29 deletions src/WebJobs.Script/Config/ScriptHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,37 @@ namespace Microsoft.Azure.WebJobs.Script
public class ScriptHost : JobHost
{
private const string HostAssemblyName = "ScriptHost";
private FileSystemWatcher _fileWatcher;

protected ScriptHost(JobHostConfiguration config, ScriptHostConfiguration scriptConfig)
: base(config)
protected ScriptHost(ScriptHostConfiguration scriptConfig)
: base(scriptConfig.HostConfig)
{
HostConfig = config;
ScriptConfig = scriptConfig;
}

public JobHostConfiguration HostConfig { get; private set; }

public ScriptHostConfiguration ScriptConfig { get; private set; }

public bool Restart { get; private set; }

protected virtual void Initialize()
{
List<FunctionDescriptorProvider> descriptionProviders = new List<FunctionDescriptorProvider>()
{
new ScriptFunctionDescriptorProvider(HostConfig, ScriptConfig.RootPath),
new NodeFunctionDescriptorProvider(HostConfig, ScriptConfig.RootPath)
new ScriptFunctionDescriptorProvider(ScriptConfig),
new NodeFunctionDescriptorProvider(this, ScriptConfig)
};

if (HostConfig.IsDevelopment)
if (ScriptConfig.HostConfig.IsDevelopment)
{
HostConfig.UseDevelopmentSettings();
ScriptConfig.HostConfig.UseDevelopmentSettings();
}

// read host.json and apply to JobHostConfiguration
string hostConfigFilePath = Path.Combine(ScriptConfig.RootPath, "host.json");
Console.WriteLine(string.Format("Reading host configuration file '{0}'", hostConfigFilePath));
string json = File.ReadAllText(hostConfigFilePath);
JObject hostConfig = JObject.Parse(json);
ApplyConfiguration(hostConfig, HostConfig);
ApplyConfiguration(hostConfig, ScriptConfig);

// read all script functions and apply to JobHostConfiguration
Collection<FunctionDescriptor> functions = ReadFunctions(ScriptConfig, descriptionProviders);
Expand All @@ -57,8 +57,33 @@ protected virtual void Initialize()
List<Type> types = new List<Type>();
types.Add(type);

HostConfig.TypeLocator = new TypeLocator(types);
HostConfig.NameResolver = new NameResolver();
ScriptConfig.HostConfig.TypeLocator = new TypeLocator(types);
ScriptConfig.HostConfig.NameResolver = new NameResolver();

if (ScriptConfig.WatchFiles)
{
_fileWatcher = new FileSystemWatcher(ScriptConfig.RootPath, "*.json")
{
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
_fileWatcher.Changed += OnConfigurationFileChanged;
}
}

private void StopAndRestart()
{
if (Restart)
{
// we've already received a restart call
return;
}

Console.WriteLine("The host configuration file has changed. Restarting.");

// Flag for restart and stop the host.
Restart = true;
Stop();
}

public static ScriptHost Create(ScriptHostConfiguration scriptConfig = null)
Expand All @@ -76,8 +101,7 @@ public static ScriptHost Create(ScriptHostConfiguration scriptConfig = null)
scriptConfig.RootPath = Path.Combine(Environment.CurrentDirectory, scriptConfig.RootPath);
}

JobHostConfiguration config = new JobHostConfiguration();
ScriptHost scriptHost = new ScriptHost(config, scriptConfig);
ScriptHost scriptHost = new ScriptHost(scriptConfig);
scriptHost.Initialize();

return scriptHost;
Expand Down Expand Up @@ -180,14 +204,22 @@ internal static Collection<FunctionDescriptor> ReadFunctions(List<FunctionFolder
return functionDescriptors;
}

internal static void ApplyConfiguration(JObject config, JobHostConfiguration jobHostConfig)
internal static void ApplyConfiguration(JObject config, ScriptHostConfiguration scriptConfig)
{
JobHostConfiguration hostConfig = scriptConfig.HostConfig;

JToken hostId = (JToken)config["id"];
if (hostId == null)
{
throw new InvalidOperationException("An 'id' must be specified in the host configuration.");
}
jobHostConfig.HostId = (string)hostId;
hostConfig.HostId = (string)hostId;

JToken watchFiles = (JToken)config["watchFiles"];
if (watchFiles != null && watchFiles.Type == JTokenType.Boolean)
{
scriptConfig.WatchFiles = (bool)watchFiles;
}

// Apply Queues configuration
JObject configSection = (JObject)config["queues"];
Expand All @@ -196,19 +228,19 @@ internal static void ApplyConfiguration(JObject config, JobHostConfiguration job
{
if (configSection.TryGetValue("maxPollingInterval", out value))
{
jobHostConfig.Queues.MaxPollingInterval = TimeSpan.FromMilliseconds((int)value);
hostConfig.Queues.MaxPollingInterval = TimeSpan.FromMilliseconds((int)value);
}
if (configSection.TryGetValue("batchSize", out value))
{
jobHostConfig.Queues.BatchSize = (int)value;
hostConfig.Queues.BatchSize = (int)value;
}
if (configSection.TryGetValue("maxDequeueCount", out value))
{
jobHostConfig.Queues.MaxDequeueCount = (int)value;
hostConfig.Queues.MaxDequeueCount = (int)value;
}
if (configSection.TryGetValue("newBatchThreshold", out value))
{
jobHostConfig.Queues.NewBatchThreshold = (int)value;
hostConfig.Queues.NewBatchThreshold = (int)value;
}
}

Expand All @@ -219,23 +251,23 @@ internal static void ApplyConfiguration(JObject config, JobHostConfiguration job
{
if (configSection.TryGetValue("lockPeriod", out value))
{
jobHostConfig.Singleton.LockPeriod = TimeSpan.Parse((string)value);
hostConfig.Singleton.LockPeriod = TimeSpan.Parse((string)value);
}
if (configSection.TryGetValue("listenerLockPeriod", out value))
{
jobHostConfig.Singleton.ListenerLockPeriod = TimeSpan.Parse((string)value);
hostConfig.Singleton.ListenerLockPeriod = TimeSpan.Parse((string)value);
}
if (configSection.TryGetValue("listenerLockRecoveryPollingInterval", out value))
{
jobHostConfig.Singleton.ListenerLockRecoveryPollingInterval = TimeSpan.Parse((string)value);
hostConfig.Singleton.ListenerLockRecoveryPollingInterval = TimeSpan.Parse((string)value);
}
if (configSection.TryGetValue("lockAcquisitionTimeout", out value))
{
jobHostConfig.Singleton.LockAcquisitionTimeout = TimeSpan.Parse((string)value);
hostConfig.Singleton.LockAcquisitionTimeout = TimeSpan.Parse((string)value);
}
if (configSection.TryGetValue("lockAcquisitionPollingInterval", out value))
{
jobHostConfig.Singleton.LockAcquisitionPollingInterval = TimeSpan.Parse((string)value);
hostConfig.Singleton.LockAcquisitionPollingInterval = TimeSpan.Parse((string)value);
}
}

Expand All @@ -250,7 +282,7 @@ internal static void ApplyConfiguration(JObject config, JobHostConfiguration job
sbConfig.MessageOptions.MaxConcurrentCalls = (int)value;
}
}
jobHostConfig.UseServiceBus(sbConfig);
hostConfig.UseServiceBus(sbConfig);

// Apply Tracing configuration
configSection = (JObject)config["tracing"];
Expand All @@ -259,7 +291,7 @@ internal static void ApplyConfiguration(JObject config, JobHostConfiguration job
TraceLevel consoleLevel;
if (Enum.TryParse<TraceLevel>((string)value, true, out consoleLevel))
{
jobHostConfig.Tracing.ConsoleLevel = consoleLevel;
hostConfig.Tracing.ConsoleLevel = consoleLevel;
}
}

Expand All @@ -270,9 +302,33 @@ internal static void ApplyConfiguration(JObject config, JobHostConfiguration job
{
webHooksConfig = new WebHooksConfiguration((int)value);
}
jobHostConfig.UseWebHooks(webHooksConfig);
hostConfig.UseWebHooks(webHooksConfig);

hostConfig.UseTimers();
}

private void OnConfigurationFileChanged(object sender, FileSystemEventArgs e)
{
string fileName = Path.GetFileName(e.Name);

if (!Restart &&
((string.Compare(fileName, "host.json") == 0) || string.Compare(fileName, "function.json") == 0))
{
StopAndRestart();
}
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_fileWatcher != null)
{
_fileWatcher.Dispose();
}
}

jobHostConfig.UseTimers();
base.Dispose(disposing);
}
}
}
21 changes: 21 additions & 0 deletions src/WebJobs.Script/Config/ScriptHostConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;

namespace Microsoft.Azure.WebJobs.Script
{
public class ScriptHostConfiguration
{
public ScriptHostConfiguration()
{
HostConfig = new JobHostConfiguration();
WatchFiles = true;
}

/// <summary>
/// Gets the <see cref="JobHostConfiguration"/>.
/// </summary>
public JobHostConfiguration HostConfig { get; private set; }

/// <summary>
/// Gets or sets the path to the script function directory.
/// </summary>
public string RootPath { get; set; }

/// <summary>
/// Gets or sets a value dicating whether the <see cref="ScriptHost"/> should
/// monitor file for changes (default is true). When set to true, the host will
/// automatically react to source/config file changes. When set to false no file
/// monitoring will be performed.
/// </summary>
public bool WatchFiles { get; set; }
}
}
26 changes: 15 additions & 11 deletions src/WebJobs.Script/Description/FunctionDescriptorProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@ public abstract class FunctionDescriptorProvider
{
public abstract bool TryCreate(FunctionFolderInfo functionFolderInfo, out FunctionDescriptor functionDescriptor);

protected bool IsDisabled(string functionName, JObject trigger)
protected bool IsDisabled(string functionName, JObject value)
{
if (value != null && IsDisabled(value["disabled"]))
{
Console.WriteLine(string.Format("Function '{0}' is disabled", functionName));
return true;
}

return false;
}

private bool IsDisabled(JToken isDisabledValue)
{
bool isDisabled = false;
JToken isDisabledValue = trigger["disabled"];
if (isDisabledValue != null)
{
if (isDisabledValue.Type == JTokenType.Boolean && (bool)isDisabledValue)
{
isDisabled = true;
return true;
}
else
{
Expand All @@ -32,17 +41,12 @@ protected bool IsDisabled(string functionName, JObject trigger)
(string.Compare(value, "1", StringComparison.OrdinalIgnoreCase) == 0 ||
string.Compare(value, "true", StringComparison.OrdinalIgnoreCase) == 0))
{
isDisabled = true;
return true;
}
}
}

if (isDisabled)
{
Console.WriteLine(string.Format("Function '{0}' is disabled", functionName));
}

return isDisabled;
return false;
}

protected ParameterDescriptor ParseQueueTrigger(JObject trigger, Type triggerParameterType = null)
Expand Down
Loading

0 comments on commit 7b702c6

Please sign in to comment.