Actors that serialise file access

I wonder if actors are a good choice to protect resources from concurrent access, for example a directory of files. In the past I have implemented this scenario using dispatch queues. What are the pros and cons of using an actor as a funnel point for blocking file access APIs?

1 Like

If you use async calls, your environment also has to be async. So async calls are “infectious” in this sense, which is very OK if you are in an async environment anyway. So in order to support both async and non-async environments, dispatch queues should be a good choice and I use them for exactly this use case.

(I generally try to support both types of environments where appropriate, e.g. by implementing a property named async which gives you a version of some object that functions in an async environment but actually does the same thing.)

…What do others think about this topic?

What are those operations specifically? Creating / deleting files and folders? Reading from / writing to existing files? Do you want to protect yourself from a situation that another app can, say, create a new file when you are iterating your directory, or reads from a file that your app wants to delete?

Just the typical CRUD operations in the file system. I want to protect the integrity of these files by serialising access to them. An actor won't help to serialise access from other applications because it's a different process - but that's not the problem I want to solve.

EDIT: Sequence of CRUD operations that shouldn't be interrupted.

I just need to serialise synchronous calls in my case. Due to actor re-entrancy, call order is not guaranteed when using async methods within actor methods.
My question is more about whether it is a good idea to use an actor to serialize access not to its properties, but to a file system resource.

Actors won't help you here. Even aside from actor reentrancy considerations, there is no guarantee of order of execution of actor methods called from sites outside the actor.

CRUD operations are already thread-safe. (It'd be a fairly disappointing file system if they weren't.) Since actors don't "serialize" anything in the sense of executing methods in the order they were called, you don't need an actor.

What you may need is a FIFO queue, and that's the benefit that a (serial) DispatchQueue solution brings to the party.

Now, if you're talking about making sequences of operations effectively "atomic" (e.g. you aren't allowed to mutate a directory while someone is enumerating it), then you have some mutable state that an actor can protect. That's at a higher level of abstraction than CRUD, I think. In that regard, I think @tera's questions are more relevant here than you might think.



1 Like

You're right, I wasn't precise enough. What I am interested in is a synchronous and uninterrupted sequence of CRUD operations.

There is pretty much the answer in this thread: Actors (without custom executors) use the global cooperative default thread pool. So blocking calls within the actor methods would block one of the few available threads. this is certainly not ideal, and dispatch queues don't have this problem.

I mean… dispatch queues do have that problem, they just respond to it in a different way up to a limit.

If you're considering a lot of concurrent io traffic, I guess you should take a look at posix aio for a truly asynchronous api (Perhaps someone already made a wrapper lib)

About aio on macOS.

I think it's ok to use actors if you expose your higher level API to work with atomic operation sequences, e.g. like so (a quick & dirty example):

actor Logger {
    func log(_ string: String) async {
        let text = read()
        write(text + string)
    static func log(_ string: String) {
        Task {
            await Self.shared.log(string)

I don't think this Logger example would be recommended because 1) it participates in starving the thread pool and 2) order is not guaranteed.

1 Like