Skip to content

Commit 678cb04

Browse files
committed
Adds blocking file locks.
Fixes #26665 [email protected] Review URL: https://codereview.chromium.org/2050413002 .
1 parent 6914b84 commit 678cb04

10 files changed

+232
-16
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 1.18.0
2+
3+
### Core library changes
4+
5+
* `dart:io`
6+
* Adds file locking modes `FileLock.BLOCKING_SHARED` and
7+
`FileLock.BLOCKING_EXCLUSIVE`.
8+
19
## 1.17.1 - 2016-06-10
210

311
Patch release, resolves two issues:

runtime/bin/file.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ class File : public ReferenceCounted<File> {
8181
kLockUnlock = 0,
8282
kLockShared = 1,
8383
kLockExclusive = 2,
84-
kLockMax = 2
84+
kLockBlockingShared = 3,
85+
kLockBlockingExclusive = 4,
86+
kLockMax = 4
8587
};
8688

8789
intptr_t GetFD();

runtime/bin/file_android.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
139139
fl.l_type = F_UNLCK;
140140
break;
141141
case File::kLockShared:
142+
case File::kLockBlockingShared:
142143
fl.l_type = F_RDLCK;
143144
break;
144145
case File::kLockExclusive:
146+
case File::kLockBlockingExclusive:
145147
fl.l_type = F_WRLCK;
146148
break;
147149
default:
@@ -150,9 +152,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
150152
fl.l_whence = SEEK_SET;
151153
fl.l_start = start;
152154
fl.l_len = end == -1 ? 0 : end - start;
153-
// fcntl does not block, but fails if the lock cannot be acquired.
154-
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
155-
return rc != -1;
155+
int cmd = F_SETLK;
156+
if ((lock == File::kLockBlockingShared) ||
157+
(lock == File::kLockBlockingExclusive)) {
158+
cmd = F_SETLKW;
159+
}
160+
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
156161
}
157162

158163

runtime/bin/file_linux.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
136136
fl.l_type = F_UNLCK;
137137
break;
138138
case File::kLockShared:
139+
case File::kLockBlockingShared:
139140
fl.l_type = F_RDLCK;
140141
break;
141142
case File::kLockExclusive:
143+
case File::kLockBlockingExclusive:
142144
fl.l_type = F_WRLCK;
143145
break;
144146
default:
@@ -147,9 +149,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
147149
fl.l_whence = SEEK_SET;
148150
fl.l_start = start;
149151
fl.l_len = end == -1 ? 0 : end - start;
150-
// fcntl does not block, but fails if the lock cannot be acquired.
151-
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
152-
return rc != -1;
152+
int cmd = F_SETLK;
153+
if ((lock == File::kLockBlockingShared) ||
154+
(lock == File::kLockBlockingExclusive)) {
155+
cmd = F_SETLKW;
156+
}
157+
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
153158
}
154159

155160

runtime/bin/file_macos.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
139139
fl.l_type = F_UNLCK;
140140
break;
141141
case File::kLockShared:
142+
case File::kLockBlockingShared:
142143
fl.l_type = F_RDLCK;
143144
break;
144145
case File::kLockExclusive:
146+
case File::kLockBlockingExclusive:
145147
fl.l_type = F_WRLCK;
146148
break;
147149
default:
@@ -150,9 +152,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
150152
fl.l_whence = SEEK_SET;
151153
fl.l_start = start;
152154
fl.l_len = end == -1 ? 0 : end - start;
153-
// fcntl does not block, but fails if the lock cannot be acquired.
154-
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
155-
return rc != -1;
155+
int cmd = F_SETLK;
156+
if ((lock == File::kLockBlockingShared) ||
157+
(lock == File::kLockBlockingExclusive)) {
158+
cmd = F_SETLKW;
159+
}
160+
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
156161
}
157162

158163

runtime/bin/file_win.cc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,15 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
138138
rc = UnlockFileEx(handle, 0, length_low, length_high, &overlapped);
139139
break;
140140
case File::kLockShared:
141-
case File::kLockExclusive: {
142-
DWORD flags = LOCKFILE_FAIL_IMMEDIATELY;
143-
if (lock == File::kLockExclusive) {
141+
case File::kLockExclusive:
142+
case File::kLockBlockingShared:
143+
case File::kLockBlockingExclusive: {
144+
DWORD flags = 0;
145+
if ((lock == File::kLockShared) || (lock == File::kLockExclusive)) {
146+
flags |= LOCKFILE_FAIL_IMMEDIATELY;
147+
}
148+
if ((lock == File::kLockExclusive) ||
149+
(lock == File::kLockBlockingExclusive)) {
144150
flags |= LOCKFILE_EXCLUSIVE_LOCK;
145151
}
146152
rc = LockFileEx(handle, flags, 0,

sdk/lib/io/file.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ enum FileLock {
5252
/// Shared file lock.
5353
SHARED,
5454
/// Exclusive file lock.
55-
EXCLUSIVE
55+
EXCLUSIVE,
56+
/// Blocking shared file lock.
57+
BLOCKING_SHARED,
58+
/// Blocking exclusive file lock.
59+
BLOCKING_EXCLUSIVE,
5660
}
5761

5862
/**
@@ -735,6 +739,11 @@ abstract class RandomAccessFile {
735739
*
736740
* To obtain an exclusive lock on a file it must be opened for writing.
737741
*
742+
* If [mode] is [FileLock.EXCLUSIVE] or [FileLock.SHARED], an error is
743+
* signaled if the lock cannot be obtained. If [mode] is
744+
* [FileLock.BLOCKING_EXCLUSIVE] or [FileLock.BLOCKING_SHARED], the
745+
* returned [Future] is resolved only when the lock has been obtained.
746+
*
738747
* *NOTE* file locking does have slight differences in behavior across
739748
* platforms:
740749
*
@@ -768,6 +777,11 @@ abstract class RandomAccessFile {
768777
*
769778
* To obtain an exclusive lock on a file it must be opened for writing.
770779
*
780+
* If [mode] is [FileLock.EXCLUSIVE] or [FileLock.SHARED], an exception is
781+
* thrown if the lock cannot be obtained. If [mode] is
782+
* [FileLock.BLOCKING_EXCLUSIVE] or [FileLock.BLOCKING_SHARED], the
783+
* call returns only after the lock has been obtained.
784+
*
771785
* *NOTE* file locking does have slight differences in behavior across
772786
* platforms:
773787
*

sdk/lib/io/file_impl.dart

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,18 @@ class _RandomAccessFile implements RandomAccessFile {
919919
static final int LOCK_UNLOCK = 0;
920920
static final int LOCK_SHARED = 1;
921921
static final int LOCK_EXCLUSIVE = 2;
922+
static final int LOCK_BLOCKING_SHARED = 3;
923+
static final int LOCK_BLOCKING_EXCLUSIVE = 4;
924+
925+
int _fileLockValue(FileLock fl) {
926+
switch (fl) {
927+
case FileLock.SHARED: return LOCK_SHARED;
928+
case FileLock.EXCLUSIVE: return LOCK_EXCLUSIVE;
929+
case FileLock.BLOCKING_SHARED: return LOCK_BLOCKING_SHARED;
930+
case FileLock.BLOCKING_EXCLUSIVE: return LOCK_BLOCKING_EXCLUSIVE;
931+
default: return -1;
932+
}
933+
}
922934

923935
Future<RandomAccessFile> lock(
924936
[FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end = -1]) {
@@ -928,7 +940,7 @@ class _RandomAccessFile implements RandomAccessFile {
928940
if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) {
929941
throw new ArgumentError();
930942
}
931-
int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED;
943+
int lock = _fileLockValue(mode);
932944
return _dispatch(_FILE_LOCK, [null, lock, start, end])
933945
.then((response) {
934946
if (_isErrorResponse(response)) {
@@ -963,7 +975,7 @@ class _RandomAccessFile implements RandomAccessFile {
963975
if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) {
964976
throw new ArgumentError();
965977
}
966-
int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED;
978+
int lock = _fileLockValue(mode);
967979
var result = _ops.lock(lock, start, end);
968980
if (result is OSError) {
969981
throw new FileSystemException('lock failed', path, result);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
//
5+
// Script used by the file_lock_test.dart test.
6+
7+
import "dart:async";
8+
import "dart:io";
9+
10+
Future<int> testLockWholeFile(File file, int len) async {
11+
var raf = await file.open(mode: APPEND);
12+
await raf.setPosition(0);
13+
int nextToWrite = 1;
14+
while (nextToWrite <= len) {
15+
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, len);
16+
17+
int at;
18+
int p;
19+
while (true) {
20+
p = await raf.position();
21+
at = await raf.readByte();
22+
if (at == 0 || at == -1) break;
23+
nextToWrite++;
24+
}
25+
await raf.setPosition(p);
26+
await raf.writeByte(nextToWrite);
27+
await raf.flush();
28+
nextToWrite++;
29+
await raf.unlock(0, len);
30+
}
31+
32+
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, len);
33+
await raf.setPosition(0);
34+
for (int i = 1; i <= len; i++) {
35+
if ((await raf.readByte()) != i) {
36+
await raf.unlock(0, len);
37+
await raf.close();
38+
return 1;
39+
}
40+
}
41+
await raf.unlock(0, len);
42+
await raf.close();
43+
return 0;
44+
}
45+
46+
main(List<String> args) async {
47+
File file = new File(args[0]);
48+
int len = int.parse(args[1]);
49+
exit(await testLockWholeFile(file, len));
50+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// This test works by spawning a new process running
6+
// file_blocking_lock_script.dart, trading the file lock back and forth,
7+
// writing bytes 1 ... 25 in order to the file. There are checks to ensure
8+
// that the bytes are written in order, that one process doesn't write all the
9+
// bytes and that a non-blocking lock fails such that a blocking lock must
10+
// be taken, which succeeds.
11+
12+
import 'dart:async';
13+
import 'dart:convert';
14+
import 'dart:io';
15+
16+
import "package:async_helper/async_helper.dart";
17+
import "package:expect/expect.dart";
18+
import "package:path/path.dart";
19+
20+
// Check whether the file is locked or not.
21+
runPeer(String path, int len, FileLock mode) {
22+
var script = Platform.script.resolve(
23+
'file_blocking_lock_script.dart').toFilePath();
24+
var arguments = []
25+
..addAll(Platform.executableArguments)
26+
..add(script)
27+
..add(path)
28+
..add(len.toString());
29+
return Process.start(Platform.executable, arguments).then((process) {
30+
process.stdout
31+
.transform(UTF8.decoder)
32+
.listen((data) { print(data); });
33+
process.stderr
34+
.transform(UTF8.decoder)
35+
.listen((data) { print(data); });
36+
return process;
37+
});
38+
}
39+
40+
testLockWholeFile() async {
41+
const int length = 25;
42+
asyncStart();
43+
Directory directory = await Directory.systemTemp.createTemp('dart_file_lock');
44+
File file = new File(join(directory.path, "file"));
45+
await file.writeAsBytes(new List.filled(length, 0));
46+
var raf = await file.open(mode: APPEND);
47+
await raf.setPosition(0);
48+
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, length);
49+
Process peer = await runPeer(file.path, length, FileLock.BLOCKING_EXCLUSIVE);
50+
51+
// Wait a bit for the other process to get started. We'll synchronize on
52+
// the file lock.
53+
await new Future.delayed(const Duration(seconds: 1));
54+
55+
int nextToWrite = 1;
56+
int at = 0;
57+
List iWrote = new List.filled(length, 0);
58+
bool nonBlockingFailed = false;
59+
while (nextToWrite <= length) {
60+
int p = await raf.position();
61+
await raf.writeByte(nextToWrite);
62+
await raf.flush();
63+
// Record which bytes this process wrote so that we can check that the
64+
// other process was able to take the lock and write some bytes.
65+
iWrote[nextToWrite-1] = nextToWrite;
66+
nextToWrite++;
67+
await raf.unlock(0, length);
68+
try {
69+
await raf.lock(FileLock.EXCLUSIVE, 0, length);
70+
} catch(e) {
71+
// Check that at some point the non-blocking lock fails.
72+
nonBlockingFailed = true;
73+
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, length);
74+
}
75+
while (true) {
76+
p = await raf.position();
77+
at = await raf.readByte();
78+
if (at == 0 || at == -1) break;
79+
nextToWrite++;
80+
}
81+
await raf.setPosition(p);
82+
}
83+
84+
await raf.setPosition(0);
85+
for (int i = 1; i <= length; i++) {
86+
Expect.equals(i, await raf.readByte());
87+
}
88+
await raf.unlock(0, length);
89+
90+
bool wroteAll = true;
91+
for (int i = 0; i < length; i++) {
92+
// If there's a 0 entry, this process didn't write all bytes.
93+
wroteAll = wroteAll && (iWrote[i] == 0);
94+
}
95+
Expect.equals(false, wroteAll);
96+
97+
Expect.equals(true, nonBlockingFailed);
98+
99+
peer.exitCode.then((v) {
100+
Expect.equals(0, v);
101+
raf.closeSync();
102+
directory.deleteSync(recursive: true);
103+
asyncEnd();
104+
});
105+
}
106+
107+
main() {
108+
testLockWholeFile();
109+
}

0 commit comments

Comments
 (0)