SIGABRT Signal 6 Abort trap

I got crash report for my mobile application

    private var _timedEvents: SynchronizedBarrier<[String: TimeInterval]>
    private var timedEvents: [String: TimeInterval] {
        get {
            _timedEvents.value
        }
        set {
            _timedEvents.value { $0 = newValue }
        }
    }

    func time(event: String) {
        let startTime = Date.now.timeIntervalSince1970
        trackingQueue.async { [weak self, startTime, event] in
            guard let self else { return }
            var timedEvents = self.timedEvents
            timedEvents[event] = startTime
            self.timedEvents = timedEvents
        }
    }

From the report, the crash is happening at _timedEvents.value { $0 = newValue }

struct ReadWriteLock {
    private let concurentQueue: DispatchQueue

    init(label: String,
         qos: DispatchQoS = .utility) {
        let queue = DispatchQueue(label: label,
                                  qos: qos,
                                  attributes: .concurrent)
        self.init(queue: queue)
    }

    init(queue: DispatchQueue) {
        self.concurentQueue = queue
    }

    func read<T>(closure: () -> T) -> T {
        concurentQueue.sync { closure() }
    }

    func write<T>(closure: () throws -> T) rethrows -> T {
        try concurentQueue.sync(flags: .barrier) { try closure() }
    }
}

struct SynchronizedBarrier<Value> {
    private let lock: ReadWriteLock

    private var _value: Value

    init(_ value: Value,
         lock: ReadWriteLock = ReadWriteLock(queue: DispatchQueue(label: "com.example.SynchronizedBarrier",
                                                                  attributes: .concurrent))) {
        self.lock = lock
        self._value = value
    }

    var value: Value { lock.read { _value } }

    mutating func value<T>(execute task: (inout Value) throws -> T) rethrows -> T {
        try lock.write { try task(&_value) }
    }
}

What could be the reason for the crash? I have attached the crash report.

Answered by CMDdev in 790456022
 var value: Value { lock.read { _value } }

Isn't SynchronizedBarrier<Value>.value a read-only computed property (i.e. doesn't have a setter), and thus you can't set _timedEvents.value to a new value using _timedEvents.value { $0 = newValue }?

I'd try changing SynchronizedBarrier<Value>.value to this:

var value: Value { 
    get {
        lock.read { _value }
    }
    set {
        lock.write { _value = newValue }
    }
}

I haven't tested it, so let me know if this works or not.

_timedEvents.value { $0 = newValue }

I'm curious, is there a reason why _timedEvents.value = newValue doesn't work? Never mind, I think I understand now.

Looking at the crash report and at the Swift runtime, it seems that something results in a dangling reference, since it calls fatalError with this description:

    swift::fatalError(0,
                      "Object %p of class %s deallocated with non-zero retain "
                      "count %zd. This object's deinit, or something called "
                      "from it, may have created a strong reference to self "
                      "which outlived deinit, resulting in a dangling "
                      "reference.\n",
                      object,
                      descriptor ? descriptor->Name.get() : "<unknown>",
                      retainCount);

I think the way you're using nested computed properties somehow results in a dangling reference. Perhaps the object causing this is newValue, though I'm not sure.

Maybe you could reproduce the issue and record the logs with the Console app on Mac to see if you find any other clues?

 var value: Value { lock.read { _value } }

Isn't SynchronizedBarrier<Value>.value a read-only computed property (i.e. doesn't have a setter), and thus you can't set _timedEvents.value to a new value using _timedEvents.value { $0 = newValue }?

I'd try changing SynchronizedBarrier<Value>.value to this:

var value: Value { 
    get {
        lock.read { _value }
    }
    set {
        lock.write { _value = newValue }
    }
}

I haven't tested it, so let me know if this works or not.

I don't know if this is the cause, but these lines of code smell wrong to me:

            var timedEvents = self.timedEvents
            timedEvents[event] = startTime
            self.timedEvents = timedEvents

The problem is that the lock is released between when you read timedEvents and when you write it; if another thread writes a change between the read and the write, you'll lose it. Additionally, because you are copying out the dictionary, modifying it, and assigning it back instead of updating it in place, you're ensuring there will always be two copies of it, so Swift will have to make a new copy of the backing storage for the dictionary every time you change it.

The solution to this kind of issue is to change the code so that the read, change, and write all happen without releasing the lock between them. This means you won't be able to use a setter, and will instead have to provide a closure-based API to modify timedEvents in place, while the lock is held, without any danger of intervening writes:

    private var timedEvents: [String: TimeInterval] {
        get {
            _timedEvents.value
        }
    }
    private func updateTimedEvents(_ body: (inout [String: TimeInterval]) -> Void {
        _timedEvents.value(body)
    }

Once you've made that change, you can do this:

            self.updateTimedEvents { timedEvents in
                timedEvents[event] = startTime
            }

I can't guarantee that will prevent your crash (although it might—it should make this code deallocate far fewer objects!), but it should improve performance and correctness.

@Developer Tools Engineer that makes sense and alligns with the dangling references error i found in the runtime. I think that could be the cause.

I wanted to add that I didn't notice the following function when I sent my second reply on this thread:

mutating func value<T>(execute task: (inout Value) throws -> T) rethrows -> T {
        try lock.write { try task(&_value) }
    }

So yes, _timedEvents.value { $0 = newValue } is correct (and if it wasn't, it should've turned in a compile-time error). Sorry for the confusion.

If anyone from Apple could remove the " Recommended" tag from my message I would appreciate it, as I was wrong. :)

SIGABRT Signal 6 Abort trap
 
 
Q