Adds blocking file locks.

Fixes #26665

R=sgjesse@google.com

Review URL: https://codereview.chromium.org/2050413002 .
This commit is contained in:
Zachary Anderson 2016-06-13 07:58:09 -07:00
parent 6914b84a03
commit 678cb048ed
10 changed files with 232 additions and 16 deletions

View file

@ -1,3 +1,11 @@
## 1.18.0
### Core library changes
* `dart:io`
* Adds file locking modes `FileLock.BLOCKING_SHARED` and
`FileLock.BLOCKING_EXCLUSIVE`.
## 1.17.1 - 2016-06-10
Patch release, resolves two issues:

View file

@ -81,7 +81,9 @@ class File : public ReferenceCounted<File> {
kLockUnlock = 0,
kLockShared = 1,
kLockExclusive = 2,
kLockMax = 2
kLockBlockingShared = 3,
kLockBlockingExclusive = 4,
kLockMax = 4
};
intptr_t GetFD();

View file

@ -139,9 +139,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_type = F_UNLCK;
break;
case File::kLockShared:
case File::kLockBlockingShared:
fl.l_type = F_RDLCK;
break;
case File::kLockExclusive:
case File::kLockBlockingExclusive:
fl.l_type = F_WRLCK;
break;
default:
@ -150,9 +152,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_whence = SEEK_SET;
fl.l_start = start;
fl.l_len = end == -1 ? 0 : end - start;
// fcntl does not block, but fails if the lock cannot be acquired.
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
return rc != -1;
int cmd = F_SETLK;
if ((lock == File::kLockBlockingShared) ||
(lock == File::kLockBlockingExclusive)) {
cmd = F_SETLKW;
}
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
}

View file

@ -136,9 +136,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_type = F_UNLCK;
break;
case File::kLockShared:
case File::kLockBlockingShared:
fl.l_type = F_RDLCK;
break;
case File::kLockExclusive:
case File::kLockBlockingExclusive:
fl.l_type = F_WRLCK;
break;
default:
@ -147,9 +149,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_whence = SEEK_SET;
fl.l_start = start;
fl.l_len = end == -1 ? 0 : end - start;
// fcntl does not block, but fails if the lock cannot be acquired.
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
return rc != -1;
int cmd = F_SETLK;
if ((lock == File::kLockBlockingShared) ||
(lock == File::kLockBlockingExclusive)) {
cmd = F_SETLKW;
}
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
}

View file

@ -139,9 +139,11 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_type = F_UNLCK;
break;
case File::kLockShared:
case File::kLockBlockingShared:
fl.l_type = F_RDLCK;
break;
case File::kLockExclusive:
case File::kLockBlockingExclusive:
fl.l_type = F_WRLCK;
break;
default:
@ -150,9 +152,12 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
fl.l_whence = SEEK_SET;
fl.l_start = start;
fl.l_len = end == -1 ? 0 : end - start;
// fcntl does not block, but fails if the lock cannot be acquired.
int rc = fcntl(handle_->fd(), F_SETLK, &fl);
return rc != -1;
int cmd = F_SETLK;
if ((lock == File::kLockBlockingShared) ||
(lock == File::kLockBlockingExclusive)) {
cmd = F_SETLKW;
}
return TEMP_FAILURE_RETRY(fcntl(handle_->fd(), cmd, &fl)) != -1;
}

View file

@ -138,9 +138,15 @@ bool File::Lock(File::LockType lock, int64_t start, int64_t end) {
rc = UnlockFileEx(handle, 0, length_low, length_high, &overlapped);
break;
case File::kLockShared:
case File::kLockExclusive: {
DWORD flags = LOCKFILE_FAIL_IMMEDIATELY;
if (lock == File::kLockExclusive) {
case File::kLockExclusive:
case File::kLockBlockingShared:
case File::kLockBlockingExclusive: {
DWORD flags = 0;
if ((lock == File::kLockShared) || (lock == File::kLockExclusive)) {
flags |= LOCKFILE_FAIL_IMMEDIATELY;
}
if ((lock == File::kLockExclusive) ||
(lock == File::kLockBlockingExclusive)) {
flags |= LOCKFILE_EXCLUSIVE_LOCK;
}
rc = LockFileEx(handle, flags, 0,

View file

@ -52,7 +52,11 @@ enum FileLock {
/// Shared file lock.
SHARED,
/// Exclusive file lock.
EXCLUSIVE
EXCLUSIVE,
/// Blocking shared file lock.
BLOCKING_SHARED,
/// Blocking exclusive file lock.
BLOCKING_EXCLUSIVE,
}
/**
@ -735,6 +739,11 @@ abstract class RandomAccessFile {
*
* To obtain an exclusive lock on a file it must be opened for writing.
*
* If [mode] is [FileLock.EXCLUSIVE] or [FileLock.SHARED], an error is
* signaled if the lock cannot be obtained. If [mode] is
* [FileLock.BLOCKING_EXCLUSIVE] or [FileLock.BLOCKING_SHARED], the
* returned [Future] is resolved only when the lock has been obtained.
*
* *NOTE* file locking does have slight differences in behavior across
* platforms:
*
@ -768,6 +777,11 @@ abstract class RandomAccessFile {
*
* To obtain an exclusive lock on a file it must be opened for writing.
*
* If [mode] is [FileLock.EXCLUSIVE] or [FileLock.SHARED], an exception is
* thrown if the lock cannot be obtained. If [mode] is
* [FileLock.BLOCKING_EXCLUSIVE] or [FileLock.BLOCKING_SHARED], the
* call returns only after the lock has been obtained.
*
* *NOTE* file locking does have slight differences in behavior across
* platforms:
*

View file

@ -919,6 +919,18 @@ class _RandomAccessFile implements RandomAccessFile {
static final int LOCK_UNLOCK = 0;
static final int LOCK_SHARED = 1;
static final int LOCK_EXCLUSIVE = 2;
static final int LOCK_BLOCKING_SHARED = 3;
static final int LOCK_BLOCKING_EXCLUSIVE = 4;
int _fileLockValue(FileLock fl) {
switch (fl) {
case FileLock.SHARED: return LOCK_SHARED;
case FileLock.EXCLUSIVE: return LOCK_EXCLUSIVE;
case FileLock.BLOCKING_SHARED: return LOCK_BLOCKING_SHARED;
case FileLock.BLOCKING_EXCLUSIVE: return LOCK_BLOCKING_EXCLUSIVE;
default: return -1;
}
}
Future<RandomAccessFile> lock(
[FileLock mode = FileLock.EXCLUSIVE, int start = 0, int end = -1]) {
@ -928,7 +940,7 @@ class _RandomAccessFile implements RandomAccessFile {
if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) {
throw new ArgumentError();
}
int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED;
int lock = _fileLockValue(mode);
return _dispatch(_FILE_LOCK, [null, lock, start, end])
.then((response) {
if (_isErrorResponse(response)) {
@ -963,7 +975,7 @@ class _RandomAccessFile implements RandomAccessFile {
if ((start < 0) || (end < -1) || ((end != -1) && (start >= end))) {
throw new ArgumentError();
}
int lock = (mode == FileLock.EXCLUSIVE) ? LOCK_EXCLUSIVE : LOCK_SHARED;
int lock = _fileLockValue(mode);
var result = _ops.lock(lock, start, end);
if (result is OSError) {
throw new FileSystemException('lock failed', path, result);

View file

@ -0,0 +1,50 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Script used by the file_lock_test.dart test.
import "dart:async";
import "dart:io";
Future<int> testLockWholeFile(File file, int len) async {
var raf = await file.open(mode: APPEND);
await raf.setPosition(0);
int nextToWrite = 1;
while (nextToWrite <= len) {
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, len);
int at;
int p;
while (true) {
p = await raf.position();
at = await raf.readByte();
if (at == 0 || at == -1) break;
nextToWrite++;
}
await raf.setPosition(p);
await raf.writeByte(nextToWrite);
await raf.flush();
nextToWrite++;
await raf.unlock(0, len);
}
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, len);
await raf.setPosition(0);
for (int i = 1; i <= len; i++) {
if ((await raf.readByte()) != i) {
await raf.unlock(0, len);
await raf.close();
return 1;
}
}
await raf.unlock(0, len);
await raf.close();
return 0;
}
main(List<String> args) async {
File file = new File(args[0]);
int len = int.parse(args[1]);
exit(await testLockWholeFile(file, len));
}

View file

@ -0,0 +1,109 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// This test works by spawning a new process running
// file_blocking_lock_script.dart, trading the file lock back and forth,
// writing bytes 1 ... 25 in order to the file. There are checks to ensure
// that the bytes are written in order, that one process doesn't write all the
// bytes and that a non-blocking lock fails such that a blocking lock must
// be taken, which succeeds.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import "package:async_helper/async_helper.dart";
import "package:expect/expect.dart";
import "package:path/path.dart";
// Check whether the file is locked or not.
runPeer(String path, int len, FileLock mode) {
var script = Platform.script.resolve(
'file_blocking_lock_script.dart').toFilePath();
var arguments = []
..addAll(Platform.executableArguments)
..add(script)
..add(path)
..add(len.toString());
return Process.start(Platform.executable, arguments).then((process) {
process.stdout
.transform(UTF8.decoder)
.listen((data) { print(data); });
process.stderr
.transform(UTF8.decoder)
.listen((data) { print(data); });
return process;
});
}
testLockWholeFile() async {
const int length = 25;
asyncStart();
Directory directory = await Directory.systemTemp.createTemp('dart_file_lock');
File file = new File(join(directory.path, "file"));
await file.writeAsBytes(new List.filled(length, 0));
var raf = await file.open(mode: APPEND);
await raf.setPosition(0);
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, length);
Process peer = await runPeer(file.path, length, FileLock.BLOCKING_EXCLUSIVE);
// Wait a bit for the other process to get started. We'll synchronize on
// the file lock.
await new Future.delayed(const Duration(seconds: 1));
int nextToWrite = 1;
int at = 0;
List iWrote = new List.filled(length, 0);
bool nonBlockingFailed = false;
while (nextToWrite <= length) {
int p = await raf.position();
await raf.writeByte(nextToWrite);
await raf.flush();
// Record which bytes this process wrote so that we can check that the
// other process was able to take the lock and write some bytes.
iWrote[nextToWrite-1] = nextToWrite;
nextToWrite++;
await raf.unlock(0, length);
try {
await raf.lock(FileLock.EXCLUSIVE, 0, length);
} catch(e) {
// Check that at some point the non-blocking lock fails.
nonBlockingFailed = true;
await raf.lock(FileLock.BLOCKING_EXCLUSIVE, 0, length);
}
while (true) {
p = await raf.position();
at = await raf.readByte();
if (at == 0 || at == -1) break;
nextToWrite++;
}
await raf.setPosition(p);
}
await raf.setPosition(0);
for (int i = 1; i <= length; i++) {
Expect.equals(i, await raf.readByte());
}
await raf.unlock(0, length);
bool wroteAll = true;
for (int i = 0; i < length; i++) {
// If there's a 0 entry, this process didn't write all bytes.
wroteAll = wroteAll && (iWrote[i] == 0);
}
Expect.equals(false, wroteAll);
Expect.equals(true, nonBlockingFailed);
peer.exitCode.then((v) {
Expect.equals(0, v);
raf.closeSync();
directory.deleteSync(recursive: true);
asyncEnd();
});
}
main() {
testLockWholeFile();
}