groue / GRDBCombine

GRDB ❤️ Combine
MIT License
223 stars 16 forks source link

Write publisher never completes. #13

Closed scottandrew closed 5 years ago

scottandrew commented 5 years ago

I am trying the following code with combine on a DBPool.

let cancellable = dbPool.writePublisher(updates: { (db) -> Int  in
    let league = League(id: "123", name: "foo", alias: "foo")
    try league.insert(db)
    return try League.fetchCount(db)
})
.sink(receiveCompletion: { error in
    print("copletion")
}, receiveValue: { count in
    print ("\(count)")
})

however the sink or any other calls inserted in the chain are never called when the publisher completes.

groue commented 5 years ago

Hello @scottandrew, and thanks for trying GRDBCombine!

The write publisher tests pass, so something is different in your setup and we need to find out what.

Would you please perform a few checks?

scottandrew commented 5 years ago

Sorry my fault. This is Xcode 11 beta 6 and the latest GRDBCombine. I am seeing this on a MacOS 10.15 beta 6 In a small command line app. The only event handleRequests get is receiveRequest. The apps complete code is below. The cancellable is held on too since we are just in a while loop below.

struct League: Codable, FetchableRecord, PersistableRecord {
    enum CodingKeys: String, CodingKey {
        case id
        case name
        case alias
    }

    let id: String
    let name: String
    let alias: String
}

let cancellable = dbPool.writePublisher(updates: { (db) -> Int  in
    let league = League(id: "123", name: "foo", alias: "foo")
    try league.insert(db)
    return try League.fetchCount(db)
})
.sink(receiveCompletion: { error in
    print("copletion")
}, receiveValue: { count in
    print ("\(count)")
    done = true
})

while (!done) {}

This originally came up because I was flat mapping to the publisher after a json was decoded to write to the disc.

groue commented 5 years ago

Ha, this is a deadlock. Just there:

while (!done) {}

The write publisher is documented to complete on the main thread by default. But it can't, because the main thread is busy waiting for the done flag. The completion is scheduled... but it can never be executed, and the done flag remains false.

So... As a general rule, be synchronous if you can, because it's just so much simpler! Do you really need Combine in a command line app?

// Synchronous database access
let count = try dbPool.write { db -> Int in
    let league = League(id: "123", name: "foo", alias: "foo")
    try league.insert(db)
    return try League.fetchCount(db)
}
// Just use count already
print(count)

If you must use asynchronous APIs, then... use another waiting technique. There is no one-size-fits-all solution, so I'll let you find the best solution that matches your real app.

Edit: you may look for solutions based on RunLoop, dispatchMain, DispatchSemaphore, or DispatchGroup... One of them should do the trick.

scottandrew commented 5 years ago

I'm dumb. Thats what I get for doing things at night after 10 hrs of work. ;-) Works great when I move off main. ;-)

Love GRDB and this library. I used GRDB in the Neil Young Archives app and it was super valuable.

groue commented 5 years ago

I'm super glad I could help you making a super app which makes people happy 😄🎶🎸