Skip to content

Commit

Permalink
Merge pull request #91 from Lombiq/issue/OSOE-751
Browse files Browse the repository at this point in the history
OSOE-751: Upgrade to Orchard Core 1.8
  • Loading branch information
Psichorex authored Feb 21, 2024
2 parents 7cd6537 + 96c28d7 commit 6ee339a
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 130 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<ItemGroup>
<!-- Adding Node.js Extensions as a PackageReference will result in inclusion of its .props and .targets files
during Build. -->
<PackageReference Include="Lombiq.NodeJs.Extensions" Version="1.3.2" />
<PackageReference Include="Lombiq.NodeJs.Extensions" Version="1.3.3-alpha.0.osoe-751" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<!-- This is necessary to support netstandard2.0. -->
<LangVersion>7.3</LangVersion>
<DefaultItemExcludes>$(DefaultItemExcludes);.git*;node_modules\**</DefaultItemExcludes>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand Down
71 changes: 36 additions & 35 deletions Lombiq.NodeJs.Extensions/CustomExecTasks/ExclusiveMutex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,54 @@
using System.Diagnostics;
using System.Threading;

namespace Lombiq.NodeJs.Extensions.CustomExecTasks;

public class ExclusiveMutex
namespace Lombiq.NodeJs.Extensions.CustomExecTasks
{
private readonly string _mutexName;
private readonly TimeSpan _timeout;
public class ExclusiveMutex
{
private readonly string _mutexName;
private readonly TimeSpan _timeout;

public int RetryIntervalMs { get; set; } = 100;
public int WaitTimeMs { get; set; } = 1000;
public int RetryIntervalMs { get; set; } = 100;
public int WaitTimeMs { get; set; } = 1000;

public ExclusiveMutex(string mutexName, TimeSpan timeout)
{
_mutexName = mutexName;
_timeout = timeout;
}
public ExclusiveMutex(string mutexName, TimeSpan timeout)
{
_mutexName = mutexName;
_timeout = timeout;
}

public bool Execute(
Func<bool> functionToExecute, Action<string, object[]> logWait = null, Action<string, object[]> logError = null)
{
var count = 1;
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed <= _timeout)
public bool Execute(
Func<bool> functionToExecute, Action<string, object[]> logWait = null, Action<string, object[]> logError = null)
{
using (var mutex = new Mutex(initiallyOwned: false, _mutexName, out var createdNew))
var count = 1;
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed <= _timeout)
{
// We only try to acquire the mutex in case it was freshly created, because that means that no other
// processes are currently using it, including in a shared way.
if (createdNew && mutex.WaitOne(WaitTimeMs))
using (var mutex = new Mutex(initiallyOwned: false, _mutexName, out var createdNew))
{
try
{
logWait?.Invoke(
"Acquired exclusive access to {0} after {1}.", new object[] { _mutexName, stopwatch.Elapsed });
return functionToExecute();
}
finally
// We only try to acquire the mutex in case it was freshly created, because that means that no other
// processes are currently using it, including in a shared way.
if (createdNew && mutex.WaitOne(WaitTimeMs))
{
mutex.ReleaseMutex();
try
{
logWait?.Invoke(
"Acquired exclusive access to {0} after {1}.", new object[] { _mutexName, stopwatch.Elapsed });
return functionToExecute();
}
finally
{
mutex.ReleaseMutex();
}
}
}

logWait?.Invoke("#{0} Waiting for exclusive access to {1}.", new object[] { count++, _mutexName });
Thread.Sleep(RetryIntervalMs);
}

logWait?.Invoke("#{0} Waiting for exclusive access to {1}.", new object[] { count++, _mutexName });
Thread.Sleep(RetryIntervalMs);
logError?.Invoke("Failed to acquire exclusive access {0} in {1}.", new object[] { _mutexName, _timeout });
return false;
}

logError?.Invoke("Failed to acquire exclusive access {0} in {1}.", new object[] { _mutexName, _timeout });
return false;
}
}
123 changes: 66 additions & 57 deletions Lombiq.NodeJs.Extensions/CustomExecTasks/ExecWithMutex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,78 @@
using System;
using System.Threading;

namespace Lombiq.NodeJs.Extensions.CustomExecTasks;

/// <summary>
/// An Exec task, wrapped around a critical section.
/// </summary>
/// <remarks>
/// <para>
/// We want to synchronize many shared readers and exclusive writers. Here's how to do that using a <see cref="Mutex"/>.
/// </para>
/// <list type="number">
/// <item>
/// <description>
/// Any reader process simply creates a Mutex instance without calling WaitOne() for the time of execution. This will
/// signal to the writer, that at least one reader is already relying on that Mutex. At the same, this approach still
/// allows other readers to proceed in parallel. To assure that no writer is currently using the Mutex, any reader will
/// call WaitOne(0) on it. If that call fails, it means that a writer is currently using the Mutex, and we have to wait.
/// </description>
/// </item>
/// <item>
/// <description>
/// The writer process first creates a Mutex instance and checks whether it was created just now. If yes, it will
/// process to "lock" the Mutex by calling WaitOne() on it. If not, it means that a reader is currently processing and
/// the writer needs to retry (after a short wait time).
/// </description>
/// </item>
/// </list>
/// </remarks>
public class ExecWithMutex : Exec
namespace Lombiq.NodeJs.Extensions.CustomExecTasks
{
public MutexAccess MutexAccessToUse =>
Enum.TryParse(Access, ignoreCase: true, out MutexAccess access) ? access : MutexAccess.Undefined;

public TimeSpan TimeoutSpan => TimeSpan.FromSeconds(TimeoutSeconds);

/// <summary>
/// Gets or sets the mutex name.
/// An Exec task, wrapped around a critical section.
/// </summary>
[Required]
public string MutexName { get; set; }
/// <remarks>
/// <para>
/// We want to synchronize many shared readers and exclusive writers. Here's how to do that using a <see cref="Mutex"/>.
/// </para>
/// <list type="number">
/// <item>
/// <description>
/// Any reader process simply creates a Mutex instance without calling WaitOne() for the time of execution. This
/// will signal to the writer, that at least one reader is already relying on that Mutex. At the same, this approach
/// still allows other readers to proceed in parallel. To assure that no writer is currently using the Mutex, any
/// reader will call WaitOne(0) on it. If that call fails, it means that a writer is currently using the Mutex, and
/// we have to wait.
/// </description>
/// </item>
/// <item>
/// <description>
/// The writer process first creates a Mutex instance and checks whether it was created just now. If yes, it will
/// process to "lock" the Mutex by calling WaitOne() on it. If not, it means that a reader is currently processing
/// and the writer needs to retry (after a short wait time).
/// </description>
/// </item>
/// </list>
/// </remarks>
public class ExecWithMutex : Exec
{
public MutexAccess MutexAccessToUse =>
Enum.TryParse(Access, ignoreCase: true, out MutexAccess access) ? access : MutexAccess.Undefined;

/// <summary>
/// Gets or sets the <see cref="MutexAccess"/> level to use on this Mutex.
/// </summary>
[Required]
public string Access { get; set; }
public TimeSpan TimeoutSpan => TimeSpan.FromSeconds(TimeoutSeconds);

/// <summary>
/// Gets or sets the maximum number of seconds that any thread should wait for the given mutex.
/// </summary>
[Required]
public int TimeoutSeconds { get; set; }
/// <summary>
/// Gets or sets the mutex name.
/// </summary>
[Required]
public string MutexName { get; set; }

/// <inheritdoc />
public override bool Execute() => MutexAccessToUse switch
{
MutexAccess.Shared => new SharedMutex(MutexName, TimeoutSpan)
.Execute(base.Execute, Log.LogMessage, Log.LogError),
/// <summary>
/// Gets or sets the <see cref="MutexAccess"/> level to use on this Mutex.
/// </summary>
[Required]
public string Access { get; set; }

/// <summary>
/// Gets or sets the maximum number of seconds that any thread should wait for the given mutex.
/// </summary>
[Required]
public int TimeoutSeconds { get; set; }

MutexAccess.Exclusive => new ExclusiveMutex(MutexName, TimeoutSpan)
.Execute(base.Execute, Log.LogMessage, Log.LogError),
/// <inheritdoc />
public override bool Execute()
{
switch (MutexAccessToUse)
{
case MutexAccess.Shared:
return new SharedMutex(MutexName, TimeoutSpan)
.Execute(base.Execute, Log.LogMessage, Log.LogError);

_ => throw new ArgumentException(
$"The \"{nameof(Access)}\" attribute on the {nameof(ExecWithMutex)} task needs to be set to either " +
$"\"{nameof(MutexAccess.Shared)}\" or \"{nameof(MutexAccess.Exclusive)}\"!"),
};
case MutexAccess.Exclusive:
return new ExclusiveMutex(MutexName, TimeoutSpan)
.Execute(base.Execute, Log.LogMessage, Log.LogError);
case MutexAccess.Undefined:
return false;
default:
throw new ArgumentException(
$"The \"{nameof(Access)}\" attribute on the {nameof(ExecWithMutex)} task needs to be set to either " +
$"\"{nameof(MutexAccess.Shared)}\" or \"{nameof(MutexAccess.Exclusive)}\"!");
}
}
}
}
13 changes: 7 additions & 6 deletions Lombiq.NodeJs.Extensions/CustomExecTasks/MutexAccess.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
namespace Lombiq.NodeJs.Extensions.CustomExecTasks;

public enum MutexAccess
namespace Lombiq.NodeJs.Extensions.CustomExecTasks
{
Undefined,
Exclusive,
Shared,
public enum MutexAccess
{
Undefined,
Exclusive,
Shared,
}
}
61 changes: 31 additions & 30 deletions Lombiq.NodeJs.Extensions/CustomExecTasks/SharedMutex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,49 @@
using System.Diagnostics;
using System.Threading;

namespace Lombiq.NodeJs.Extensions.CustomExecTasks;

public class SharedMutex
namespace Lombiq.NodeJs.Extensions.CustomExecTasks
{
private readonly string _mutexName;
private readonly TimeSpan _timeout;
public class SharedMutex
{
private readonly string _mutexName;
private readonly TimeSpan _timeout;

public int RetryIntervalMs { get; set; } = 100;
public int RetryIntervalMs { get; set; } = 100;

public SharedMutex(string mutexName, TimeSpan timeout)
{
_mutexName = mutexName;
_timeout = timeout;
}
public SharedMutex(string mutexName, TimeSpan timeout)
{
_mutexName = mutexName;
_timeout = timeout;
}

public bool Execute(
Func<bool> functionToExecute, Action<string, object[]> logWait = null, Action<string, object[]> logError = null)
{
var count = 1;
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed <= _timeout)
public bool Execute(
Func<bool> functionToExecute, Action<string, object[]> logWait = null, Action<string, object[]> logError = null)
{
using (var mutex = new Mutex(initiallyOwned: false, _mutexName))
var count = 1;
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed <= _timeout)
{
if (mutex.WaitOne(0))
using (var mutex = new Mutex(initiallyOwned: false, _mutexName))
{
// Release the mutex asap because we don't need it for execution. We only needed it to verify that
// it is currently not "locked", i.e. in exclusive usage.
mutex.ReleaseMutex();
if (mutex.WaitOne(0))
{
// Release the mutex asap because we don't need it for execution. We only needed it to verify
// that it is currently not "locked", i.e. in exclusive usage.
mutex.ReleaseMutex();

logWait?.Invoke("Acquired shared access to {0} in {1}.", new object[] { _mutexName, stopwatch.Elapsed });
logWait?.Invoke("Acquired shared access to {0} in {1}.", new object[] { _mutexName, stopwatch.Elapsed });

return functionToExecute();
return functionToExecute();
}
}
}

logWait?.Invoke("#{0} Waiting for shared access to {1}.", new object[] { count++, _mutexName });
logWait?.Invoke("#{0} Waiting for shared access to {1}.", new object[] { count++, _mutexName });

Thread.Sleep(RetryIntervalMs);
}
Thread.Sleep(RetryIntervalMs);
}

logError?.Invoke("Failed to acquire {0} in {1}.", new object[] { _mutexName, _timeout });
return false;
logError?.Invoke("Failed to acquire {0} in {1}.", new object[] { _mutexName, _timeout });
return false;
}
}
}
2 changes: 2 additions & 0 deletions Lombiq.NodeJs.Extensions/Lombiq.NodeJs.Extensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<!-- This is necessary to support netstandard2.0. -->
<LangVersion>7.3</LangVersion>
<DefaultItemExcludes>.git*;$(DefaultItemExcludes)</DefaultItemExcludes>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
Expand Down

0 comments on commit 6ee339a

Please sign in to comment.