Skip to content

Commit fae56fd

Browse files
committed
Handle race conditions during stream read. Refactor multiple-file lock code.
1 parent 8fabe65 commit fae56fd

File tree

4 files changed

+50
-30
lines changed

4 files changed

+50
-30
lines changed

AsyncToSyncCodeRoundtripSynchroniserMonitorNet.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,5 +179,8 @@
179179
<ItemGroup>
180180
<Content Include="copyrights.txt" />
181181
</ItemGroup>
182+
<ItemGroup>
183+
<Folder Include="icon\" />
184+
</ItemGroup>
182185
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
183186
</Project>

BinaryFileExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ namespace AsyncToSyncCodeRoundtripSynchroniserMonitor
1616
public static partial class FileExtensions
1717
{
1818
//https://stackoverflow.com/questions/18472867/checking-equality-for-two-byte-arrays/
19-
public static bool BinaryEqual(Binary a1, Binary b1)
19+
public static bool BinaryEqual(Binary a, Binary b)
2020
{
21-
return a1.Equals(b1);
21+
return a.Equals(b);
2222
}
2323

2424
public static async Task<byte[]> ReadAllBytesAsync(string path, CancellationToken cancellationToken = default(CancellationToken))
@@ -32,8 +32,9 @@ public static bool BinaryEqual(Binary a1, Binary b1)
3232
useAsync: true
3333
))
3434
{
35-
byte[] result = new byte[stream.Length];
36-
await stream.ReadAsync(result, 0, (int)stream.Length, cancellationToken);
35+
var len = (int)stream.Length; //NB! the lenght might change during the code execution, so need to save it into separate variable
36+
byte[] result = new byte[len];
37+
await stream.ReadAsync(result, 0, len, cancellationToken);
3738
return result;
3839
}
3940
}

Program.cs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -444,17 +444,7 @@ public static async Task FileUpdated(string fullName, Context context)
444444
)
445445
{
446446
var otherFullName = GetOtherFullName(fullName);
447-
var filenames = new List<string>()
448-
{
449-
fullName,
450-
otherFullName
451-
};
452-
453-
//NB! in order to avoid deadlocks, always take the locks in deterministic order
454-
filenames.Sort(StringComparer.InvariantCultureIgnoreCase);
455-
456-
using (await Global.FileOperationLocks.LockAsync(filenames[0], context.Token))
457-
using (await Global.FileOperationLocks.LockAsync(filenames[1], context.Token))
447+
using (await Global.FileOperationLocks.LockAsync(fullName, otherFullName, context.Token))
458448
{
459449
if (
460450
fullName.EndsWith("." + Global.WatchedCodeExtension)
@@ -577,17 +567,7 @@ private static async Task OnRenamedAsync(IRenamedFileSystemEvent rfse, Cancellat
577567
//NB! if file is renamed to cs~ or resx~ then that means there will be yet another write to same file, so lets skip this event here
578568
if (!rfse.FileSystemInfo.FullName.EndsWith("~"))
579569
{
580-
var filenames = new List<string>()
581-
{
582-
rfse.FileSystemInfo.FullName,
583-
rfse.PreviousFileSystemInfo.FullName
584-
};
585-
586-
//NB! in order to avoid deadlocks in case of file swaps, always take the locks in deterministic order
587-
filenames.Sort(StringComparer.InvariantCultureIgnoreCase);
588-
589-
using (await FileEventLocks.LockAsync(filenames[0], token))
590-
using (await FileEventLocks.LockAsync(filenames[1], token))
570+
using (await Global.FileOperationLocks.LockAsync(rfse.FileSystemInfo.FullName, rfse.PreviousFileSystemInfo.FullName, context.Token))
591571
{
592572
await FileUpdated(rfse.FileSystemInfo.FullName, context);
593573
await FileDeleted(rfse.PreviousFileSystemInfo.FullName, context);

Synchronisation.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ public AsyncLockWithCount()
2525

2626
public sealed class LockDictReleaser : IDisposable
2727
{
28-
public readonly string Name;
29-
public readonly AsyncLockWithCount LockEntry;
30-
public readonly IDisposable LockHandle;
31-
public readonly AsyncLockQueueDictionary AsyncLockQueueDictionary;
28+
private readonly string Name;
29+
private readonly AsyncLockWithCount LockEntry;
30+
private readonly IDisposable LockHandle;
31+
private readonly AsyncLockQueueDictionary AsyncLockQueueDictionary;
3232

3333
internal LockDictReleaser(string name, AsyncLockWithCount lockEntry, IDisposable lockHandle, AsyncLockQueueDictionary asyncLockQueueDictionary)
3434
{
@@ -76,5 +76,41 @@ private void ReleaseLock(string name, AsyncLockWithCount lockEntry, IDisposable
7676
var lockHandle = await lockEntry.LockEntry.LockAsync(cancellationToken);
7777
return new LockDictReleaser(name, lockEntry, lockHandle, this);
7878
}
79+
80+
public sealed class MultiLockDictReleaser : IDisposable
81+
{
82+
private readonly LockDictReleaser[] Releasers;
83+
84+
public MultiLockDictReleaser(params LockDictReleaser[] releasers)
85+
{
86+
this.Releasers = releasers;
87+
}
88+
89+
public void Dispose()
90+
{
91+
foreach (var releaser in Releasers)
92+
{
93+
if (releaser != null) //NB!
94+
releaser.Dispose();
95+
}
96+
}
97+
}
98+
99+
public async Task<MultiLockDictReleaser> LockAsync(string name1, string name2, CancellationToken cancellationToken = default(CancellationToken))
100+
{
101+
var names = new List<string>()
102+
{
103+
name1,
104+
name2
105+
};
106+
107+
//NB! in order to avoid deadlocks, always take the locks in deterministic order
108+
names.Sort(StringComparer.InvariantCultureIgnoreCase);
109+
110+
var releaser1 = await Global.FileOperationLocks.LockAsync(names[0], cancellationToken);
111+
var releaser2 = name1 != name2 ? await Global.FileOperationLocks.LockAsync(names[1], cancellationToken) : null;
112+
113+
return new MultiLockDictReleaser(releaser1, releaser2);
114+
}
79115
}
80116
}

0 commit comments

Comments
 (0)