Skip to content

[BUG] Possible deadlock in Async subjects #52

Description

@Lieksu

Describe the bug
It looks like subject can deadlock if task, that is observing its values is cancelled at the same time as send is called on it

Thread 1:

  • AsyncBufferedChannel.send acquires os_unfair_lock
  • AsyncBufferedChannel.send attempts to resume continuation returning element
  • Resuming continuation attempts to acquire internal lock for task status

Thread 2:

  • Task that awaits for values from subject is cancelled. This acquires internal lock for task state
  • unregister() is called and attempts to acquire os_unfair_lock

Thread 1 has os_unfair_lock, waits for internal lock
Thread 2 has internal lock, waits for os_unfair_lock

Situation can be reproduced with test like this, if run repeatedly for 100 times:

@Test
func send_deadlocks_when_cancelled_concurrently() throws {
    let subject = AsyncPassthroughSubject<Int>()
    
    let t1 = Task.detached {
        for await _ in subject { }
    }
    let barrier = DispatchSemaphore(value: 0)
    let done = DispatchSemaphore(value: 0)
    
    DispatchQueue.global().async {
        barrier.wait()
        t1.cancel()
        done.signal()
    }
    DispatchQueue.global().async {
        barrier.wait()
        subject.send(1)
        done.signal()
    }
    
    barrier.signal()
    barrier.signal()
    
    done.wait()
    done.wait()
}

It can also be reproduced using strict concurrency only, but setting up actual parallel execution is more complicated.

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions