Skip to content

Commit 61d08b7

Browse files
authored
Fix a potential race condition in FileSubscriber (#5032)
1 parent 5294e9a commit 61d08b7

File tree

1 file changed

+35
-38
lines changed

1 file changed

+35
-38
lines changed

src/core/Akka.Streams/Implementation/IO/FileSubscriber.cs

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using Akka.IO;
1414
using Akka.Streams.Actors;
1515
using Akka.Streams.IO;
16-
using Akka.Util;
1716

1817
namespace Akka.Streams.Implementation.IO
1918
{
@@ -35,20 +34,20 @@ internal class FileSubscriber : ActorSubscriber
3534
/// <exception cref="ArgumentException">TBD</exception>
3635
/// <returns>TBD</returns>
3736
public static Props Props(
38-
FileInfo f,
39-
TaskCompletionSource<IOResult> completionPromise,
40-
int bufferSize,
41-
long startPosition,
37+
FileInfo f,
38+
TaskCompletionSource<IOResult> completionPromise,
39+
int bufferSize,
40+
long startPosition,
4241
FileMode fileMode,
4342
bool autoFlush = false,
4443
object flushCommand = null)
4544
{
46-
if(bufferSize <= 0)
45+
if (bufferSize <= 0)
4746
throw new ArgumentException($"bufferSize must be > 0 (was {bufferSize})", nameof(bufferSize));
48-
if(startPosition < 0)
47+
if (startPosition < 0)
4948
throw new ArgumentException($"startPosition must be >= 0 (was {startPosition})", nameof(startPosition));
5049

51-
return Actor.Props.Create(()=> new FileSubscriber(f, completionPromise, bufferSize, startPosition, fileMode, autoFlush, flushCommand))
50+
return Actor.Props.Create(() => new FileSubscriber(f, completionPromise, bufferSize, startPosition, fileMode, autoFlush, flushCommand))
5251
.WithDeploy(Deploy.Local);
5352
}
5453

@@ -74,12 +73,12 @@ public static Props Props(
7473
/// <param name="autoFlush"></param>
7574
/// <param name="flushCommand"></param>
7675
public FileSubscriber(
77-
FileInfo f,
78-
TaskCompletionSource<IOResult> completionPromise,
79-
int bufferSize,
80-
long startPosition,
76+
FileInfo f,
77+
TaskCompletionSource<IOResult> completionPromise,
78+
int bufferSize,
79+
long startPosition,
8180
FileMode fileMode,
82-
bool autoFlush,
81+
bool autoFlush,
8382
object flushCommand)
8483
{
8584
_f = f;
@@ -111,7 +110,7 @@ protected override void PreStart()
111110
}
112111
catch (Exception ex)
113112
{
114-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
113+
CloseAndComplete(IOResult.Failed(_bytesWritten, ex));
115114
Cancel();
116115
}
117116
}
@@ -128,32 +127,23 @@ protected override bool Receive(object message)
128127
case OnNext next:
129128
try
130129
{
131-
var byteString = (ByteString) next.Element;
130+
var byteString = (ByteString)next.Element;
132131
var bytes = byteString.ToArray();
133-
try
134-
{
135-
_chan.Write(bytes, 0, bytes.Length);
136-
_bytesWritten += bytes.Length;
137-
if (_autoFlush)
138-
_chan.Flush(true);
139-
}
140-
catch (Exception ex)
141-
{
142-
_log.Error(ex, $"Tearing down FileSink({_f.FullName}) due to write error.");
143-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
144-
Context.Stop(Self);
145-
}
132+
_chan.Write(bytes, 0, bytes.Length);
133+
_bytesWritten += bytes.Length;
134+
if (_autoFlush)
135+
_chan.Flush(true);
146136
}
147137
catch (Exception ex)
148138
{
149-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
139+
CloseAndComplete(IOResult.Failed(_bytesWritten, ex));
150140
Cancel();
151141
}
152142
return true;
153143

154144
case OnError error:
155-
_log.Error(error.Cause, $"Tearing down FileSink({_f.FullName}) due to upstream error");
156-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, error.Cause));
145+
_log.Error(error.Cause, "Tearing down FileSink({0}) due to upstream error", _f.FullName);
146+
CloseAndComplete(IOResult.Failed(_bytesWritten, error.Cause));
157147
Context.Stop(Self);
158148
return true;
159149

@@ -164,8 +154,8 @@ protected override bool Receive(object message)
164154
}
165155
catch (Exception ex)
166156
{
167-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
168-
}
157+
CloseAndComplete(IOResult.Failed(_bytesWritten, ex));
158+
}
169159
Context.Stop(Self);
170160
return true;
171161

@@ -176,8 +166,8 @@ protected override bool Receive(object message)
176166
}
177167
catch (Exception ex)
178168
{
179-
_log.Error(ex, $"Tearing down FileSink({_f.FullName}). File flush failed.");
180-
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
169+
_log.Error(ex, "Tearing down FileSink({0}). File flush failed.", _f.FullName);
170+
CloseAndComplete(IOResult.Failed(_bytesWritten, ex));
181171
Context.Stop(Self);
182172
}
183173
return true;
@@ -190,18 +180,25 @@ protected override bool Receive(object message)
190180
/// TBD
191181
/// </summary>
192182
protected override void PostStop()
183+
{
184+
CloseAndComplete(IOResult.Success(_bytesWritten));
185+
base.PostStop();
186+
}
187+
188+
private void CloseAndComplete(IOResult result)
193189
{
194190
try
195191
{
192+
// close the channel/file before completing the promise, allowing the
193+
// file to be deleted, which would not work (on some systems) if the
194+
// file is still open for writing
196195
_chan?.Dispose();
196+
_completionPromise.TrySetResult(result);
197197
}
198198
catch (Exception ex)
199199
{
200200
_completionPromise.TrySetResult(IOResult.Failed(_bytesWritten, ex));
201201
}
202-
203-
_completionPromise.TrySetResult(IOResult.Success(_bytesWritten));
204-
base.PostStop();
205202
}
206203
}
207204
}

0 commit comments

Comments
 (0)