How to reconnect MySQL database in vapor?

I am using vapor framework for server side for my swift-tools-version:5.6.0

.package(url: "https://github.com/vapor/vapor.git", from: "4.75.0"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"),
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.3.0"),
.package(url: "https://github.com/autimatisering/VaporSMTPKit.git", from: "1.0.4"),

I've been setup database using seperate class,

final class Database {
    
    private(set) static var shared: Database!
   
    static func initialize(_ db: FluentKit.Database) {
        
        if let db = db as? MySQLDatabase {
            
            self.shared = .init(db)
        }
    }
    
    let db: MySQLDatabase
    
    private init(_ db: MySQLDatabase) {
        
        self.db = db
    } 

    func query(_ query: String, with continuation: CheckedContinuation<[MySQLRow]?, Error>) {
         
        self.db.query(query).whenComplete { result in
            
            switch result {
                
            case .success(let rows):
                
                if rows.isEmpty {
                   
                    continuation.resume(returning: nil)
                
                } else {
                  
                    continuation.resume(returning: rows)
                }
                
            case .failure(let error):
                
                let error = error as NSError
                
                print("Error: \(error)")
                if error.code == 11 { // Disconnected
                    
                    self.connect_db { error in
                        
                        if let error = error {
                            
                            continuation.resume(throwing: error)
                            
                        } else {
                             
                            self.query(query, with: continuation)
                        }
                    }
                     
                } else {
                    
                    continuation.resume(throwing: error)
                }
            }
        }
    }
}

In the configure I init that class like, Also I saved the App in the another class access by global.

app.databases.use(.mysql(configuration: .prod), as: .mysql)
   
    do {
        
        try await app.db.withConnection({ database in
            
            Database.initialize(database)
        })
        
    } catch {
        
        fatalError("Unable to connect database")
    }

Meanwhile If the database server closed / terminated the connection, the vapor framework doesn't reconnect automatically. I really thought 'Modern Webservers will reconnect automatically instead of throwing connection closed error'. But vapor doesn't. I've tried to reconnect using below code

Using global App:

private func connect_db(_ completionHandler:@escaping(Error?) -> Void){
        
        print("Reconnecting Database....")
        
        Task.detached(priority: .userInitiated) {
          
            do  {
                 
                try kAPI.app.db.eventLoop.close()
             
                try await kAPI.app.db.withConnection { database in
                    
                    Database.initialize(database)
                    print("Connected")
                    completionHandler(nil)
                }
                
            } catch {
                
                completionHandler(error)
            }
        }
    }

Using Current Database:

private func connect_db(_ completionHandler:@escaping(Error?) -> Void){
    print("Reconnecting Database....")
    if let db = self.db as? FluentKit.Database {
        
        Task.detached(priority: .userInitiated) {
            
            do {
                 
                try await db.withConnection({ _ in
                    
                    print("Connected")
                    completionHandler(nil)
                })
                
            } catch {
                
                print("Unable to connect!")
                completionHandler(error)
            }
        }
       
    } else {
        
        completionHandler(SERVER_ERROR("Invalid DB", info: "The Database isn't Database object", code: 28))
    }
}

I got log Connected & MySQLERROR: Connection Closed continously. So the database isn't acutally connected but it completed the withConnection closure without the error. Without DB reconnection all my work is gone to trash. DB reconnection is important for a webserver. Can anyone let me know what I am missing?

Is there any way that I can establish MySQL database reconnection in vapor framework? Thank you!

The issue you're hitting is that you don't have a connection pool and you're managing the connections yourself. I'm not sure why you have all the code you do, you seem to be making it far more complicated than needed, but essentially you'll need to catch disconnection errors in your connection and then recreate the connection. I will note that having a static mutable property is a bad idea because that's a recipe for a data race issue (turn on strict concurrency checking and you'll see a swathe of errors).

The best options you have are either just use the connection pool logic Fluent provides you for free and let it handle reconnections, or implement a connection pool that can listen for closed connections on each connection in your app and reconnect them if you need

1 Like

@0xTim Yes I used default mysql connection that documented in the vapor. I resoleved the reconnect issue by managed connection myself like you told with Fluent instead of using the vapor's documented app.database and I made reconnecion logics. This solves the issue in simple way.

Thank you for your answer!