Easy to reproduce Combine crash involving Future and concurrency (+ memory leak)

I had run into the Future deinit crash and was losing my mind until I found this thread, so thanks for having such a detailed back and forth.

The demo code above helped me to set up the crash scenario. Here's an alternate solution for a ReplayOnce publisher that behaves the same as a future but without the crash, backing it with a simple CurrentValueSubject.

public extension Publishers {

    typealias ReplayPromise<Output, Failure: Error> = (Result<Output, Failure>) -> Void

    final class ReplayOnce<Output, Failure: Error>: Publisher {

        private let currentValue = CurrentValueSubject<Output?, Failure>(nil)

        private let outputValue: AnyPublisher<Output, Failure>

        private let dataLock = NSRecursiveLock(name: "\(ReplayOnce.self):data_lock")

        public init(_ handler: @escaping ((@escaping ReplayPromise<Output, Failure>) -> Void)) {

            let current = currentValue
            let lock = dataLock

            let promise: ReplayPromise<Output, Failure> = { result in
                lock.lock()
                defer { lock.unlock() }

                switch result {
                case .success(let value):
                    current.send(value)
                    current.send(completion: .finished)
                case .failure(let error):
                    current.send(completion: .failure(error))
                }
            }

            defer {
                handler(promise)
            }

            self.outputValue = currentValue
                .compactMap { $0 }
                .eraseToAnyPublisher()
        }

        // MARK: Publisher

        public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
            dataLock.lock()
            defer { dataLock.unlock() }

            if let value = currentValue.value {
                let just = Just(value).setFailureType(to: Failure.self)
                just.receive(subscriber: subscriber)
            } else {
                outputValue.receive(subscriber: subscriber)
            }
        }
    }
}

with this class you can instiate it the same as you would a Future...

func fetch() -> AnyPublisher<Data, SomeError> {
    return Publisher.ReplayOnce { promise in
        queue.async {
           /// some work
           if let data = data {
                promise(.success(data)
           } else {
                promise(.failure(someError))
           }
        }
    }
   .eraseToAnyPublisher()
}