Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,63 @@
|
||||
#ifndef V_OS_FILELOCK_HELPERS_H
|
||||
#define V_OS_FILELOCK_HELPERS_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
static int v_filelock_lock(HANDLE handle, int exclusive, int immediate,
|
||||
unsigned long long start, unsigned long long len) {
|
||||
OVERLAPPED overlap;
|
||||
memset(&overlap, 0, sizeof(overlap));
|
||||
overlap.Offset = (DWORD)(start & 0xffffffffULL);
|
||||
overlap.OffsetHigh = (DWORD)(start >> 32);
|
||||
DWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0;
|
||||
if (exclusive) {
|
||||
flags |= LOCKFILE_EXCLUSIVE_LOCK;
|
||||
}
|
||||
DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);
|
||||
DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);
|
||||
return LockFileEx(handle, flags, 0, low, high, &overlap) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int v_filelock_unlock(HANDLE handle, unsigned long long start,
|
||||
unsigned long long len) {
|
||||
OVERLAPPED overlap;
|
||||
memset(&overlap, 0, sizeof(overlap));
|
||||
overlap.Offset = (DWORD)(start & 0xffffffffULL);
|
||||
overlap.OffsetHigh = (DWORD)(start >> 32);
|
||||
DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL);
|
||||
DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32);
|
||||
return UnlockFileEx(handle, 0, low, high, &overlap) ? 0 : -1;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int v_filelock_lock(int fd, int exclusive, int immediate,
|
||||
unsigned long long start, unsigned long long len) {
|
||||
struct flock fl;
|
||||
memset(&fl, 0, sizeof(fl));
|
||||
fl.l_type = exclusive ? F_WRLCK : F_RDLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = (off_t)start;
|
||||
fl.l_len = len == 0 ? 0 : (off_t)len;
|
||||
return fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl);
|
||||
}
|
||||
|
||||
static int v_filelock_unlock(int fd, unsigned long long start,
|
||||
unsigned long long len) {
|
||||
struct flock fl;
|
||||
memset(&fl, 0, sizeof(fl));
|
||||
fl.l_type = F_UNLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
fl.l_start = (off_t)start;
|
||||
fl.l_len = len == 0 ? 0 : (off_t)len;
|
||||
return fcntl(fd, F_SETLK, &fl);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,174 @@
|
||||
import os
|
||||
import os.filelock
|
||||
import time
|
||||
|
||||
const test_dir = os.join_path(os.vtmp_dir(), 'filelock_tests')
|
||||
|
||||
const helper_mode_env = 'V_FILELOCK_HELPER_MODE'
|
||||
const helper_path_env = 'V_FILELOCK_HELPER_PATH'
|
||||
const helper_lock_mode_env = 'V_FILELOCK_LOCK_MODE'
|
||||
const helper_start_env = 'V_FILELOCK_START'
|
||||
const helper_len_env = 'V_FILELOCK_LEN'
|
||||
|
||||
fn testsuite_begin() {
|
||||
if os.getenv(helper_mode_env) != '' {
|
||||
run_helper_process()
|
||||
}
|
||||
os.mkdir_all(test_dir) or {}
|
||||
}
|
||||
|
||||
fn testsuite_end() {
|
||||
os.rmdir_all(test_dir) or {}
|
||||
}
|
||||
|
||||
fn run_helper_process() {
|
||||
path := os.getenv(helper_path_env)
|
||||
lock_mode := if os.getenv(helper_lock_mode_env) == 'shared' {
|
||||
filelock.LockMode.shared
|
||||
} else {
|
||||
filelock.LockMode.exclusive
|
||||
}
|
||||
start := os.getenv(helper_start_env).u64()
|
||||
len := os.getenv(helper_len_env).u64()
|
||||
mut file_lock := filelock.new_file(path, filelock.LockOptions{
|
||||
mode: lock_mode
|
||||
start: start
|
||||
len: len
|
||||
})
|
||||
match os.getenv(helper_mode_env) {
|
||||
'hold' {
|
||||
file_lock.acquire() or {
|
||||
eprintln(err.msg())
|
||||
exit(2)
|
||||
}
|
||||
println('locked')
|
||||
os.flush()
|
||||
time.sleep(300 * time.millisecond)
|
||||
file_lock.release()
|
||||
exit(0)
|
||||
}
|
||||
'try' {
|
||||
exit(if file_lock.try_acquire() {
|
||||
file_lock.release()
|
||||
0
|
||||
} else {
|
||||
1
|
||||
})
|
||||
}
|
||||
else {
|
||||
exit(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn helper_path(name string) string {
|
||||
return os.join_path(test_dir, name)
|
||||
}
|
||||
|
||||
fn new_helper_process(path string, lock_mode filelock.LockMode, start u64, len u64, mode string) &os.Process {
|
||||
mut p := os.new_process(os.executable())
|
||||
mut env := os.environ()
|
||||
env[helper_mode_env] = mode
|
||||
env[helper_path_env] = path
|
||||
env[helper_lock_mode_env] = lock_mode.str()
|
||||
env[helper_start_env] = start.str()
|
||||
env[helper_len_env] = len.str()
|
||||
p.set_environment(env)
|
||||
p.set_redirect_stdio()
|
||||
return p
|
||||
}
|
||||
|
||||
fn wait_for_lock(mut p os.Process) {
|
||||
mut output := ''
|
||||
for _ in 0 .. 50 {
|
||||
if p.is_pending(.stdout) {
|
||||
output += p.stdout_read()
|
||||
if output.contains('locked') {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !p.is_alive() {
|
||||
break
|
||||
}
|
||||
time.sleep(10 * time.millisecond)
|
||||
}
|
||||
p.wait()
|
||||
stderr := p.stderr_slurp().trim_space()
|
||||
assert false, 'helper failed to acquire lock; exit=${p.code} stdout="${output.trim_space()}" stderr="${stderr}"'
|
||||
}
|
||||
|
||||
fn try_lock_from_helper(path string, lock_mode filelock.LockMode, start u64, len u64) int {
|
||||
mut p := new_helper_process(path, lock_mode, start, len, 'try')
|
||||
p.run()
|
||||
p.wait()
|
||||
stderr := p.stderr_slurp().trim_space()
|
||||
code := p.code
|
||||
p.close()
|
||||
assert stderr == ''
|
||||
return code
|
||||
}
|
||||
|
||||
fn test_flock() {
|
||||
lockfile := helper_path('test.lock')
|
||||
os.rm(lockfile) or {}
|
||||
mut l := filelock.new(lockfile)
|
||||
assert !os.exists(lockfile)
|
||||
l.acquire() or { panic(err) }
|
||||
assert os.exists(lockfile)
|
||||
l.release()
|
||||
assert !os.exists(lockfile)
|
||||
}
|
||||
|
||||
fn test_flock_try() {
|
||||
lockfile := helper_path('test-try.lock')
|
||||
os.rm(lockfile) or {}
|
||||
mut l := filelock.new(lockfile)
|
||||
assert l.try_acquire()
|
||||
l.release()
|
||||
assert !os.exists(lockfile)
|
||||
assert l.try_acquire()
|
||||
assert os.exists(lockfile)
|
||||
l.release()
|
||||
assert l.try_acquire()
|
||||
l.release()
|
||||
assert !os.exists(lockfile)
|
||||
}
|
||||
|
||||
fn test_existing_file_lock_range() {
|
||||
target := helper_path('existing-range.txt')
|
||||
os.write_file(target, '0123456789abcdef')!
|
||||
mut holder := new_helper_process(target, .exclusive, 0, 5, 'hold')
|
||||
holder.run()
|
||||
defer {
|
||||
if holder.status == .running || holder.is_alive() {
|
||||
holder.wait()
|
||||
}
|
||||
holder.close()
|
||||
}
|
||||
wait_for_lock(mut holder)
|
||||
assert try_lock_from_helper(target, .exclusive, 0, 5) == 1
|
||||
assert try_lock_from_helper(target, .exclusive, 6, 5) == 0
|
||||
holder.wait()
|
||||
assert holder.code == 0
|
||||
assert os.exists(target)
|
||||
assert os.read_file(target)! == '0123456789abcdef'
|
||||
}
|
||||
|
||||
fn test_existing_file_lock_shared_mode() {
|
||||
target := helper_path('existing-shared.txt')
|
||||
os.write_file(target, '0123456789abcdef')!
|
||||
mut holder := new_helper_process(target, .shared, 0, 5, 'hold')
|
||||
holder.run()
|
||||
defer {
|
||||
if holder.status == .running || holder.is_alive() {
|
||||
holder.wait()
|
||||
}
|
||||
holder.close()
|
||||
}
|
||||
wait_for_lock(mut holder)
|
||||
assert try_lock_from_helper(target, .shared, 0, 5) == 0
|
||||
assert try_lock_from_helper(target, .exclusive, 0, 5) == 1
|
||||
holder.wait()
|
||||
assert holder.code == 0
|
||||
assert os.exists(target)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
module filelock
|
||||
|
||||
import time
|
||||
|
||||
// LockMode describes whether an existing file lock is shared/read or exclusive/write.
|
||||
pub enum LockMode {
|
||||
shared
|
||||
exclusive
|
||||
}
|
||||
|
||||
// LockOptions configures how `new_file` locks an existing file.
|
||||
@[params]
|
||||
pub struct LockOptions {
|
||||
pub:
|
||||
mode LockMode = .exclusive
|
||||
start u64
|
||||
len u64
|
||||
}
|
||||
|
||||
enum LockTarget {
|
||||
sidecar
|
||||
file
|
||||
}
|
||||
|
||||
pub struct FileLock {
|
||||
name string
|
||||
mode LockMode = .exclusive
|
||||
start u64
|
||||
len u64
|
||||
target LockTarget = .sidecar
|
||||
mut:
|
||||
fd i64
|
||||
}
|
||||
|
||||
// new creates a sidecar lock file that is removed again when the lock is released.
|
||||
pub fn new(file_name string) FileLock {
|
||||
return FileLock{
|
||||
name: file_name
|
||||
fd: -1
|
||||
}
|
||||
}
|
||||
|
||||
// new_file creates a lock for an existing file path using OS-level file locking.
|
||||
pub fn new_file(path string, options LockOptions) FileLock {
|
||||
return FileLock{
|
||||
name: path
|
||||
mode: options.mode
|
||||
start: options.start
|
||||
len: options.len
|
||||
target: .file
|
||||
fd: -1
|
||||
}
|
||||
}
|
||||
|
||||
// wait_acquire keeps trying to acquire the lock until `timeout` expires.
|
||||
pub fn (mut l FileLock) wait_acquire(timeout time.Duration) bool {
|
||||
fin := time.now().add(timeout)
|
||||
for time.now() < fin {
|
||||
if l.try_acquire() {
|
||||
return true
|
||||
}
|
||||
time.sleep(1 * time.millisecond)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// release unlocks the file and closes the underlying file descriptor or handle.
|
||||
pub fn (mut l FileLock) release() bool {
|
||||
if l.fd != -1 {
|
||||
l.close_lock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
module filelock
|
||||
|
||||
#include <sys/file.h>
|
||||
#insert "@VEXEROOT/vlib/os/filelock/filelock_helpers.h"
|
||||
|
||||
fn C.unlink(&char) i32
|
||||
fn C.open(&char, i32, i32) i32
|
||||
fn C.close(i32) i32
|
||||
fn C.flock(i32, i32) i32
|
||||
fn C.v_filelock_lock(i32, i32, i32, u64, u64) i32
|
||||
fn C.v_filelock_unlock(i32, u64, u64) i32
|
||||
|
||||
fn (l &FileLock) open_lock() int {
|
||||
if l.target == .file {
|
||||
return open_existing_file(l.name, l.mode)
|
||||
}
|
||||
return open_lockfile(l.name)
|
||||
}
|
||||
|
||||
fn open_lockfile(path string) int {
|
||||
mut fd := C.open(&char(path.str), C.O_CREAT | C.O_RDONLY, 0o644)
|
||||
if fd == -1 {
|
||||
fd = C.open(&char(path.str), C.O_RDONLY, 0)
|
||||
}
|
||||
return fd
|
||||
}
|
||||
|
||||
fn open_existing_file(path string, mode LockMode) int {
|
||||
flags := if mode == .shared { C.O_RDONLY } else { C.O_RDWR }
|
||||
return C.open(&char(path.str), flags, 0)
|
||||
}
|
||||
|
||||
fn (l &FileLock) lock_fd(fd int, immediate bool) bool {
|
||||
if l.target == .file {
|
||||
return C.v_filelock_lock(fd, int(l.mode == .exclusive), int(immediate), l.start, l.len) == 0
|
||||
}
|
||||
flags := if immediate { C.LOCK_EX | C.LOCK_NB } else { C.LOCK_EX }
|
||||
return C.flock(fd, flags) == 0
|
||||
}
|
||||
|
||||
fn (mut l FileLock) close_lock() {
|
||||
if l.fd == -1 {
|
||||
return
|
||||
}
|
||||
fd := int(l.fd)
|
||||
if l.target == .file {
|
||||
C.v_filelock_unlock(fd, l.start, l.len)
|
||||
C.close(fd)
|
||||
} else {
|
||||
C.close(fd)
|
||||
C.unlink(&char(l.name.str))
|
||||
}
|
||||
l.fd = -1
|
||||
}
|
||||
|
||||
// acquire blocks until the lock is acquired.
|
||||
pub fn (mut l FileLock) acquire() ! {
|
||||
if l.fd != -1 {
|
||||
return error_with_code('lock already acquired by this instance', 1)
|
||||
}
|
||||
fd := l.open_lock()
|
||||
if fd == -1 {
|
||||
msg := if l.target == .file {
|
||||
'cannot open file ${l.name}'
|
||||
} else {
|
||||
'cannot create lock file ${l.name}'
|
||||
}
|
||||
return error_with_code(msg, -1)
|
||||
}
|
||||
if !l.lock_fd(fd, false) {
|
||||
C.close(fd)
|
||||
return error_with_code('cannot lock ${l.name}', -2)
|
||||
}
|
||||
l.fd = fd
|
||||
}
|
||||
|
||||
// try_acquire tries to acquire the lock without blocking.
|
||||
pub fn (mut l FileLock) try_acquire() bool {
|
||||
if l.fd != -1 {
|
||||
return true
|
||||
}
|
||||
fd := l.open_lock()
|
||||
if fd == -1 {
|
||||
return false
|
||||
}
|
||||
if !l.lock_fd(fd, true) {
|
||||
C.close(fd)
|
||||
return false
|
||||
}
|
||||
l.fd = fd
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
module filelock
|
||||
|
||||
#insert "@VEXEROOT/vlib/os/filelock/filelock_helpers.h"
|
||||
|
||||
fn C.DeleteFileW(&u16) bool
|
||||
fn C.CreateFileW(&u16, u32, u32, voidptr, u32, u32, voidptr) voidptr
|
||||
fn C.CloseHandle(voidptr) bool
|
||||
fn C.v_filelock_lock(voidptr, int, int, u64, u64) int
|
||||
fn C.v_filelock_unlock(voidptr, u64, u64) int
|
||||
|
||||
fn (l &FileLock) open_lock() i64 {
|
||||
if l.target == .file {
|
||||
return open_existing_file(l.name, l.mode)
|
||||
}
|
||||
return open_lockfile(l.name)
|
||||
}
|
||||
|
||||
fn open_lockfile(path string) i64 {
|
||||
path_wide := path.to_wide()
|
||||
handle := C.CreateFileW(path_wide, C.GENERIC_READ | C.GENERIC_WRITE, 0, 0, C.OPEN_ALWAYS,
|
||||
C.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
return file_handle(handle)
|
||||
}
|
||||
|
||||
fn open_existing_file(path string, mode LockMode) i64 {
|
||||
path_wide := path.to_wide()
|
||||
access := if mode == .shared { C.GENERIC_READ } else { C.GENERIC_READ | C.GENERIC_WRITE }
|
||||
share_mode := C.FILE_SHARE_READ | C.FILE_SHARE_WRITE | C.FILE_SHARE_DELETE
|
||||
handle := C.CreateFileW(path_wide, access, share_mode, 0, C.OPEN_EXISTING,
|
||||
C.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
return file_handle(handle)
|
||||
}
|
||||
|
||||
fn file_handle(handle voidptr) i64 {
|
||||
return if handle == voidptr(-1) { -1 } else { i64(handle) }
|
||||
}
|
||||
|
||||
fn (l &FileLock) lock_handle(handle voidptr, immediate bool) bool {
|
||||
if l.target != .file {
|
||||
return true
|
||||
}
|
||||
return C.v_filelock_lock(handle, int(l.mode == .exclusive), int(immediate), l.start, l.len) == 0
|
||||
}
|
||||
|
||||
fn (mut l FileLock) close_lock() {
|
||||
if l.fd == -1 {
|
||||
return
|
||||
}
|
||||
handle := voidptr(l.fd)
|
||||
if l.target == .file {
|
||||
C.v_filelock_unlock(handle, l.start, l.len)
|
||||
C.CloseHandle(handle)
|
||||
} else {
|
||||
C.CloseHandle(handle)
|
||||
path_wide := l.name.to_wide()
|
||||
C.DeleteFileW(path_wide)
|
||||
}
|
||||
l.fd = -1
|
||||
}
|
||||
|
||||
// acquire blocks until the lock is acquired.
|
||||
pub fn (mut l FileLock) acquire() ! {
|
||||
if l.fd != -1 {
|
||||
return error_with_code('lock already acquired by this instance', 1)
|
||||
}
|
||||
fd := l.open_lock()
|
||||
if fd == -1 {
|
||||
msg := if l.target == .file {
|
||||
'cannot open file ${l.name}'
|
||||
} else {
|
||||
'cannot create lock file ${l.name}'
|
||||
}
|
||||
return error_with_code(msg, -1)
|
||||
}
|
||||
handle := voidptr(fd)
|
||||
if !l.lock_handle(handle, false) {
|
||||
C.CloseHandle(handle)
|
||||
return error_with_code('cannot lock ${l.name}', -2)
|
||||
}
|
||||
l.fd = fd
|
||||
}
|
||||
|
||||
// try_acquire tries to acquire the lock without blocking.
|
||||
pub fn (mut l FileLock) try_acquire() bool {
|
||||
if l.fd != -1 {
|
||||
return true
|
||||
}
|
||||
fd := l.open_lock()
|
||||
if fd == -1 {
|
||||
return false
|
||||
}
|
||||
handle := voidptr(fd)
|
||||
if !l.lock_handle(handle, true) {
|
||||
C.CloseHandle(handle)
|
||||
return false
|
||||
}
|
||||
l.fd = fd
|
||||
return true
|
||||
}
|
||||
Reference in new issue
Block a user