The idea is to simplify and integrate flocks in hare-ev.
It is still a WIP cause I have a problem with a detail:
Atm ev::newflock will block until the flock is acquired. It
really isn't what we would expect from an event loop. To me it looks
okay to acquire the lock immediatly if possible, but hare-ev should
handle the case where the file already is locked, give the hand back to
the user, and call the callback after the file actually can be locked.
A solution could be to bitwise OR the operation with rt::LOCK_NB. It
make the rt::flock non-blocking, but it also prevent the user to
actually flock.
It looks possible to build an alternative route, handling the
rt::errno EWOULDBLOCK error, and storing everything needed in the
operation specific data union, to handle the flock later in
the flow.
I didn't find a good solution yet. I would like some comments and ideas!
Thanks :D
Signed-off-by: Willow Barraco <contact@willowbarraco.fr>
---
cmd/flock/main.ha | 31 +++++++++++++++++++++++++++++++
ev/+linux/file.ha | 4 ++++
ev/+linux/flock.ha | 39 +++++++++++++++++++++++++++++++++++++++
ev/+linux/loop.ha | 2 ++
4 files changed, 76 insertions(+)
create mode 100644 cmd/flock/main.ha
create mode 100644 ev/+linux/flock.ha
diff --git a/cmd/flock/main.ha b/cmd/flock/main.ha
new file mode 100644
index 0000000..5c6097c
--- /dev/null
+++ b/cmd/flock/main.ha
@@ -0,0 +1,31 @@
+use ev;
+use log;
+use time;
+use rt;
+use os;
+use io;
+use fs;
+
+export fn main() void = {
+ const loop = ev::newloop()!;
+ defer ev::finish(&loop);
+
+ let lockfile = os::create("lock", fs::mode::USER_RW)!;
+ defer io::close(lockfile)!;
+
+ log::println("waiting for lock");
+ ev::newflock(&loop, &locked, lockfile, rt::LOCK_EX)!;
+
+ for (ev::dispatch(&loop, -1)!) {
+ log::println("file freed");
+ time::sleep(1 * time::SECOND);
+
+ log::println("waiting for lock");
+ ev::newflock(&loop, &locked, lockfile, rt::LOCK_EX)!;
+ };
+};
+
+fn locked(lock: *ev::file) void = {
+ log::println("file locked");
+ time::sleep(2 * time::SECOND);
+};
diff --git a/ev/+linux/file.ha b/ev/+linux/file.ha
index 73204fe..3524b90 100644
--- a/ev/+linux/file.ha
+++ b/ev/+linux/file.ha
@@ -20,6 +20,7 @@ export type op = enum u64 {
RECVFROM = 8 << 16,
SEND = 9 << 16,
RECV = 10 << 16,
+ FLOCK = 11 << 16,
};
export type fflags = enum uint {
@@ -52,6 +53,7 @@ export type file = struct {
dest: ip::addr,
port: u16,
},
+ lockedfd: int,
},
};
@@ -157,6 +159,8 @@ fn file_epoll_ctl(file: *file) void = {
events |= rt::EPOLLIN;
case op::SEND, op::SENDTO =>
events |= rt::EPOLLOUT;
+ case op::FLOCK =>
+ events |= rt::EPOLLOUT;
case op::RECV, op::RECVFROM =>
events |= rt::EPOLLIN;
case =>
diff --git a/ev/+linux/flock.ha b/ev/+linux/flock.ha
new file mode 100644
index 0000000..f46d68a
--- /dev/null
+++ b/ev/+linux/flock.ha
@@ -0,0 +1,39 @@
+use errors;
+use io;
+use rt;
+
+// A callback which executes when a lock is acquired.
+export type lockcb = fn(file: *file) void;
+
+// Creates a new flock. Block until the flock is acquired.
+export fn newflock(
+ loop: *loop,
+ cb: *lockcb,
+ lockfile: io::file,
+ op: int,
+) (*file | rt::errno | errors::error) = {
+ const fd = match(rt::flock(lockfile, op)) {
+ case let fd: int =>
+ yield fd: io::file;
+ case let err: rt::errno =>
+ return err;
+ };
+
+ const file = register(loop, fd)?;
+ file.op = op::FLOCK;
+ file.cb = cb;
+ file.lockedfd = lockfile;
+ file_epoll_ctl(file);
+ return file;
+};
+
+fn flock_ready(flock: *file, ev: *rt::epoll_event) void = {
+ assert(flock.op == op::FLOCK);
+
+ assert(flock.cb != null);
+ const cb = flock.cb: *lockcb;
+ cb(flock);
+
+ rt::flock(flock.lockedfd, rt::LOCK_UN)!;
+ unregister(flock);
+};
diff --git a/ev/+linux/loop.ha b/ev/+linux/loop.ha
index aec5cc0..3b44dc3 100644
--- a/ev/+linux/loop.ha
+++ b/ev/+linux/loop.ha
@@ -129,6 +129,8 @@ export fn dispatch(
send_ready(file, ev);
case op::RECV =>
recv_ready(file, ev);
+ case op::FLOCK =>
+ flock_ready(file, ev);
case =>
assert(pending & ~(op::READV | op::WRITEV) == 0);
};
--
2.39.1
Btw, you might noticed I am conflicted: should hare-ev abstract
the "flock" technical aspect, and only offer "lock" in its api?
> I'm not sure that first-class support for flock is desirable. Maybe
> instead we should make it so that the user who wants to use flock can
> more easily integrate it into the event loop? Would this work fine if
> you managed flocks yourself and used ev::readable?
I failed to find a simple and clean way to have flock support in
hare-ev. So imo for now, let the user handle this outside of ev.
Most flock usage shouldn't block for too long. At least it was
acceptable for the use cases I encountered.