diff --git a/database.go b/database.go index b0d3644..fd3bec0 100644 --- a/database.go +++ b/database.go @@ -86,10 +86,16 @@ func initializeDatabase(path string) (*sql.DB, error) { accessible = true } - db, err := sql.Open("sqlite", path) - if err != nil { - return nil, fmt.Errorf("failed to create SQLite file at %q; %w", path, err) - } + // This object's connections are intended for serial writing, so we set + // IMMEDIATE transactions to make debugging of locking issues easier. + db, err := sql.Open("sqlite", path + "?_txlock=immediate") + if err != nil { + return nil, fmt.Errorf("failed to open read/write SQLite handle; %w", err) + } + + // Maxing out at one connection so that there can only be one write at any + // time; everyone else will have to block on the connection availability. + db.SetMaxOpenConns(1) // Make sure to eventually close all idle connections in the pool so that // SQLite actually commits its WAL journal. @@ -165,6 +171,19 @@ CREATE INDEX index_links ON links(tid, fid); return db, nil } +func initializeReadOnlyDatabase(path string) (*sql.DB, error) { + ro_db, err := sql.Open("sqlite", path + "?_pragma=query_only(1)") + if err != nil { + return nil, fmt.Errorf("failed to open read-only SQLite handle; %w", err) + } + + // Make sure to eventually close all idle connections in the pool so that + // SQLite actually commits its WAL journal. + ro_db.SetConnMaxIdleTime(1 * time.Minute) + + return ro_db, nil +} + /**********************************************************************/ // Pre-building the insertion statements for efficiency when iterating over and diff --git a/main.go b/main.go index b54e88b..4aa12dd 100644 --- a/main.go +++ b/main.go @@ -23,10 +23,16 @@ func main() { db, err := initializeDatabase(dbpath) if err != nil { - log.Fatalf("failed to create the initial SQLite file at %q; %v", dbpath, err) + log.Fatalf("failed to initialize SQLite file at %q; %v", dbpath, err) } defer db.Close() + ro_db, err := initializeReadOnlyDatabase(dbpath) + if err != nil { + log.Fatalf("failed to create read-only connections to %q; %v", dbpath, err) + } + defer ro_db.Close() + tokenizer, err := newUnicodeTokenizer(false) if err != nil { log.Fatalf("failed to create the default tokenizer; %v", err) @@ -49,14 +55,14 @@ func main() { // Setting up the endpoints. http.HandleFunc("POST " + prefix + "/register/start", newRegisterStartHandler(verifier)) http.HandleFunc("POST " + prefix + "/register/finish", newRegisterFinishHandler(db, verifier, tokenizer, timeout)) - http.HandleFunc("POST " + prefix + "/deregister/start", newDeregisterStartHandler(db, verifier)) + http.HandleFunc("POST " + prefix + "/deregister/start", newDeregisterStartHandler(ro_db, verifier)) http.HandleFunc("POST " + prefix + "/deregister/finish", newDeregisterFinishHandler(db, verifier, timeout)) - http.HandleFunc(prefix + "/registered", newListRegisteredDirectoriesHandler(db)) - http.HandleFunc(prefix + "/query", newQueryHandler(db, tokenizer, wild_tokenizer, "/query")) - http.HandleFunc(prefix + "/retrieve/metadata", newRetrieveMetadataHandler(db)) - http.HandleFunc(prefix + "/retrieve/file", newRetrieveFileHandler(db)) - http.HandleFunc(prefix + "/list", newListFilesHandler(db)) + http.HandleFunc(prefix + "/registered", newListRegisteredDirectoriesHandler(ro_db)) + http.HandleFunc(prefix + "/query", newQueryHandler(ro_db, tokenizer, wild_tokenizer, "/query")) + http.HandleFunc(prefix + "/retrieve/metadata", newRetrieveMetadataHandler(ro_db)) + http.HandleFunc(prefix + "/retrieve/file", newRetrieveFileHandler(ro_db)) + http.HandleFunc(prefix + "/list", newListFilesHandler(ro_db)) http.HandleFunc(prefix + "/", newDefaultHandler())