@@ -52,6 +52,7 @@ typedef struct connection
5252{
5353 sqlite3 * db ;
5454 ErlNifMutex * mutex ;
55+ ErlNifMutex * interrupt_mutex ;
5556 ErlNifPid update_hook_pid ;
5657} connection_t ;
5758
@@ -336,8 +337,14 @@ exqlite_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
336337 enif_mutex_destroy (mutex );
337338 return make_error_tuple (env , am_out_of_memory );
338339 }
339- conn -> db = db ;
340- conn -> mutex = mutex ;
340+ conn -> db = db ;
341+ conn -> mutex = mutex ;
342+ conn -> interrupt_mutex = enif_mutex_create ("exqlite:interrupt" );
343+ if (conn -> interrupt_mutex == NULL ) {
344+ // conn->db and conn->mutex are set; the destructor will clean them up.
345+ enif_release_resource (conn );
346+ return make_error_tuple (env , am_failed_to_create_mutex );
347+ }
341348
342349 result = enif_make_resource (env , conn );
343350 enif_release_resource (conn );
@@ -364,16 +371,17 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
364371 return make_error_tuple (env , am_invalid_connection );
365372 }
366373
367- // DB is already closed, nothing to do here
368- if (conn -> db == NULL ) {
369- return am_ok ;
370- }
371-
372374 // close connection in critical section to avoid race-condition
373375 // cases. Cases such as query timeout and connection pooling
374376 // attempting to close the connection
375377 connection_acquire_lock (conn );
376378
379+ // DB is already closed, nothing to do here.
380+ if (conn -> db == NULL ) {
381+ connection_release_lock (conn );
382+ return am_ok ;
383+ }
384+
377385 int autocommit = sqlite3_get_autocommit (conn -> db );
378386 if (autocommit == 0 ) {
379387 rc = sqlite3_exec (conn -> db , "ROLLBACK;" , NULL , NULL , NULL );
@@ -383,18 +391,26 @@ exqlite_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
383391 }
384392 }
385393
394+ // Hold interrupt_mutex across close+NULL so that any concurrent
395+ // exqlite_interrupt() either completes its sqlite3_interrupt() call
396+ // before we start closing, or blocks until we've both closed and
397+ // NULLed conn->db (then sees NULL and skips).
398+ //
386399 // note: _v2 may not fully close the connection, hence why we check if
387400 // any transaction is open above, to make sure other connections aren't blocked.
388401 // v1 is guaranteed to close or error, but will return error if any
389402 // unfinalized statements, which we likely have, as we rely on the destructors
390403 // to later run to clean those up
404+ enif_mutex_lock (conn -> interrupt_mutex );
391405 rc = sqlite3_close_v2 (conn -> db );
392406 if (rc != SQLITE_OK ) {
407+ enif_mutex_unlock (conn -> interrupt_mutex );
393408 connection_release_lock (conn );
394409 return make_sqlite3_error_tuple (env , rc , conn -> db );
395410 }
396-
397411 conn -> db = NULL ;
412+ enif_mutex_unlock (conn -> interrupt_mutex );
413+
398414 connection_release_lock (conn );
399415
400416 return am_ok ;
@@ -427,6 +443,11 @@ exqlite_execute(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
427443
428444 connection_acquire_lock (conn );
429445
446+ if (conn -> db == NULL ) {
447+ connection_release_lock (conn );
448+ return make_error_tuple (env , am_connection_closed );
449+ }
450+
430451 rc = sqlite3_exec (conn -> db , (char * )bin .data , NULL , NULL , NULL );
431452 if (rc != SQLITE_OK ) {
432453 connection_release_lock (conn );
@@ -456,11 +477,11 @@ exqlite_changes(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
456477 return make_error_tuple (env , am_invalid_connection );
457478 }
458479
480+ connection_acquire_lock (conn );
459481 if (conn -> db == NULL ) {
482+ connection_release_lock (conn );
460483 return make_error_tuple (env , am_connection_closed );
461484 }
462-
463- connection_acquire_lock (conn );
464485 int changes = sqlite3_changes (conn -> db );
465486 connection_release_lock (conn );
466487 return make_ok_tuple (env , enif_make_int (env , changes ));
@@ -539,6 +560,10 @@ exqlite_reset(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
539560 }
540561
541562 statement_acquire_lock (statement );
563+ if (statement -> statement == NULL ) {
564+ statement_release_lock (statement );
565+ return make_error_tuple (env , am_invalid_statement );
566+ }
542567 sqlite3_reset (statement -> statement );
543568 statement_release_lock (statement );
544569 return am_ok ;
@@ -556,6 +581,10 @@ exqlite_bind_parameter_count(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
556581 }
557582
558583 statement_acquire_lock (statement );
584+ if (statement -> statement == NULL ) {
585+ statement_release_lock (statement );
586+ return make_error_tuple (env , am_invalid_statement );
587+ }
559588 int bind_parameter_count = sqlite3_bind_parameter_count (statement -> statement );
560589 statement_release_lock (statement );
561590 return enif_make_int (env , bind_parameter_count );
@@ -580,6 +609,10 @@ exqlite_bind_parameter_index(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]
580609 }
581610
582611 statement_acquire_lock (statement );
612+ if (statement -> statement == NULL ) {
613+ statement_release_lock (statement );
614+ return make_error_tuple (env , am_invalid_statement );
615+ }
583616 int index = sqlite3_bind_parameter_index (statement -> statement , (const char * )name .data );
584617 statement_release_lock (statement );
585618 return enif_make_int (env , index );
@@ -607,6 +640,10 @@ exqlite_bind_text(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
607640 }
608641
609642 statement_acquire_lock (statement );
643+ if (statement -> statement == NULL ) {
644+ statement_release_lock (statement );
645+ return make_error_tuple (env , am_invalid_statement );
646+ }
610647 int rc = sqlite3_bind_text (statement -> statement , idx , (char * )text .data , text .size , SQLITE_TRANSIENT );
611648 statement_release_lock (statement );
612649 return enif_make_int (env , rc );
@@ -634,6 +671,10 @@ exqlite_bind_blob(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
634671 }
635672
636673 statement_acquire_lock (statement );
674+ if (statement -> statement == NULL ) {
675+ statement_release_lock (statement );
676+ return make_error_tuple (env , am_invalid_statement );
677+ }
637678 int rc = sqlite3_bind_blob (statement -> statement , idx , (char * )blob .data , blob .size , SQLITE_TRANSIENT );
638679 statement_release_lock (statement );
639680 return enif_make_int (env , rc );
@@ -661,6 +702,10 @@ exqlite_bind_integer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
661702 }
662703
663704 statement_acquire_lock (statement );
705+ if (statement -> statement == NULL ) {
706+ statement_release_lock (statement );
707+ return make_error_tuple (env , am_invalid_statement );
708+ }
664709 int rc = sqlite3_bind_int64 (statement -> statement , idx , i );
665710 statement_release_lock (statement );
666711 return enif_make_int (env , rc );
@@ -688,6 +733,10 @@ exqlite_bind_float(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
688733 }
689734
690735 statement_acquire_lock (statement );
736+ if (statement -> statement == NULL ) {
737+ statement_release_lock (statement );
738+ return make_error_tuple (env , am_invalid_statement );
739+ }
691740 int rc = sqlite3_bind_double (statement -> statement , idx , f );
692741 statement_release_lock (statement );
693742 return enif_make_int (env , rc );
@@ -710,6 +759,10 @@ exqlite_bind_null(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
710759 }
711760
712761 statement_acquire_lock (statement );
762+ if (statement -> statement == NULL ) {
763+ statement_release_lock (statement );
764+ return make_error_tuple (env , am_invalid_statement );
765+ }
713766 int rc = sqlite3_bind_null (statement -> statement , idx );
714767 statement_release_lock (statement );
715768 return enif_make_int (env , rc );
@@ -756,6 +809,11 @@ exqlite_multi_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
756809
757810 connection_acquire_lock (conn );
758811
812+ if (statement -> statement == NULL ) {
813+ connection_release_lock (conn );
814+ return make_error_tuple (env , am_invalid_statement );
815+ }
816+
759817 ERL_NIF_TERM rows = enif_make_list_from_array (env , NULL , 0 );
760818 for (int i = 0 ; i < chunk_size ; i ++ ) {
761819 ERL_NIF_TERM row ;
@@ -817,6 +875,11 @@ exqlite_step(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
817875
818876 connection_acquire_lock (conn );
819877
878+ if (statement -> statement == NULL ) {
879+ connection_release_lock (conn );
880+ return make_error_tuple (env , am_invalid_statement );
881+ }
882+
820883 int rc = sqlite3_step (statement -> statement );
821884 switch (rc ) {
822885 case SQLITE_ROW :
@@ -866,6 +929,10 @@ exqlite_columns(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
866929 }
867930
868931 statement_acquire_lock (statement );
932+ if (statement -> statement == NULL ) {
933+ statement_release_lock (statement );
934+ return make_error_tuple (env , am_invalid_statement );
935+ }
869936 size = sqlite3_column_count (statement -> statement );
870937
871938 if (size == 0 ) {
@@ -920,6 +987,10 @@ exqlite_last_insert_rowid(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
920987 }
921988
922989 connection_acquire_lock (conn );
990+ if (conn -> db == NULL ) {
991+ connection_release_lock (conn );
992+ return make_error_tuple (env , am_connection_closed );
993+ }
923994 sqlite3_int64 last_rowid = sqlite3_last_insert_rowid (conn -> db );
924995 connection_release_lock (conn );
925996 return make_ok_tuple (env , enif_make_int64 (env , last_rowid ));
@@ -947,11 +1018,13 @@ exqlite_transaction_status(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
9471018 // and then re-opens a new connection. There is a condition where by
9481019 // the connection's database is not set but the calling elixir / erlang
9491020 // pass an incomplete reference.
1021+ // Check must be inside the lock: a concurrent close() can set conn->db = NULL
1022+ // between the pre-lock check and the sqlite3_get_autocommit() call → segfault.
1023+ connection_acquire_lock (conn );
9501024 if (!conn -> db ) {
1025+ connection_release_lock (conn );
9511026 return make_ok_tuple (env , am_error );
9521027 }
953-
954- connection_acquire_lock (conn );
9551028 int autocommit = sqlite3_get_autocommit (conn -> db );
9561029 connection_release_lock (conn );
9571030
@@ -986,6 +1059,11 @@ exqlite_serialize(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
9861059
9871060 connection_acquire_lock (conn );
9881061
1062+ if (conn -> db == NULL ) {
1063+ connection_release_lock (conn );
1064+ return make_error_tuple (env , am_connection_closed );
1065+ }
1066+
9891067 buffer = sqlite3_serialize (conn -> db , (char * )database_name .data , & buffer_size , 0 );
9901068 if (!buffer ) {
9911069 connection_release_lock (conn );
@@ -1032,15 +1110,22 @@ exqlite_deserialize(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
10321110
10331111 connection_acquire_lock (conn );
10341112
1113+ if (conn -> db == NULL ) {
1114+ connection_release_lock (conn );
1115+ return make_error_tuple (env , am_connection_closed );
1116+ }
1117+
10351118 size = serialized .size ;
10361119 buffer = sqlite3_malloc (size );
10371120 if (!buffer ) {
1121+ connection_release_lock (conn );
10381122 return make_error_tuple (env , am_deserialization_failed );
10391123 }
10401124
10411125 memcpy (buffer , serialized .data , size );
1042- rc = sqlite3_deserialize (conn -> db , "main" , buffer , size , size , flags );
1126+ rc = sqlite3_deserialize (conn -> db , ( const char * ) database_name . data , buffer , size , size , flags );
10431127 if (rc != SQLITE_OK ) {
1128+ sqlite3_free (buffer );
10441129 connection_release_lock (conn );
10451130 return make_sqlite3_error_tuple (env , rc , conn -> db );
10461131 }
@@ -1102,6 +1187,11 @@ connection_type_destructor(ErlNifEnv* env, void* arg)
11021187 enif_mutex_destroy (conn -> mutex );
11031188 conn -> mutex = NULL ;
11041189 }
1190+
1191+ if (conn -> interrupt_mutex ) {
1192+ enif_mutex_destroy (conn -> interrupt_mutex );
1193+ conn -> interrupt_mutex = NULL ;
1194+ }
11051195}
11061196
11071197void
@@ -1245,10 +1335,18 @@ exqlite_enable_load_extension(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[
12451335 return make_error_tuple (env , am_invalid_enable_load_extension_value );
12461336 }
12471337
1338+ connection_acquire_lock (conn );
1339+ if (conn -> db == NULL ) {
1340+ connection_release_lock (conn );
1341+ return make_error_tuple (env , am_connection_closed );
1342+ }
12481343 rc = sqlite3_enable_load_extension (conn -> db , enable_load_extension_value );
12491344 if (rc != SQLITE_OK ) {
1250- return make_sqlite3_error_tuple (env , rc , conn -> db );
1345+ ERL_NIF_TERM err = make_sqlite3_error_tuple (env , rc , conn -> db );
1346+ connection_release_lock (conn );
1347+ return err ;
12511348 }
1349+ connection_release_lock (conn );
12521350 return am_ok ;
12531351}
12541352
@@ -1312,6 +1410,11 @@ exqlite_set_update_hook(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
13121410
13131411 connection_acquire_lock (conn );
13141412
1413+ if (conn -> db == NULL ) {
1414+ connection_release_lock (conn );
1415+ return make_error_tuple (env , am_connection_closed );
1416+ }
1417+
13151418 // Passing the connection as the third argument causes it to be
13161419 // passed as the first argument to update_callback. This allows us
13171420 // to extract the hook pid and reset the hook if the pid is not alive.
@@ -1395,14 +1498,20 @@ exqlite_interrupt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
13951498 return make_error_tuple (env , am_invalid_connection );
13961499 }
13971500
1398- // DB is already closed, nothing to do here
1399- if (conn -> db == NULL ) {
1400- return am_ok ;
1501+ // We deliberately do NOT hold the connection lock here. A running
1502+ // query holds the lock for its entire duration; acquiring it in
1503+ // interrupt() would block until the query finishes, which defeats
1504+ // the purpose of interrupting it.
1505+ //
1506+ // interrupt_mutex is a dedicated lightweight lock shared with close().
1507+ // close() holds the connection lock (so any running query has already
1508+ // released it) then acquires interrupt_mutex before nulling conn->db.
1509+ // interrupt() acquires interrupt_mutex here, so the two cannot overlap.
1510+ enif_mutex_lock (conn -> interrupt_mutex );
1511+ if (conn -> db != NULL ) {
1512+ sqlite3_interrupt (conn -> db );
14011513 }
1402-
1403- // connection_acquire_lock(conn);
1404- sqlite3_interrupt (conn -> db );
1405- // connection_release_lock(conn);
1514+ enif_mutex_unlock (conn -> interrupt_mutex );
14061515
14071516 return am_ok ;
14081517}
@@ -1416,10 +1525,18 @@ exqlite_errmsg(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
14161525
14171526 if (enif_get_resource (env , argv [0 ], connection_type , (void * * )& conn )) {
14181527 connection_acquire_lock (conn );
1528+ if (conn -> db == NULL ) {
1529+ connection_release_lock (conn );
1530+ return make_error_tuple (env , am_connection_closed );
1531+ }
14191532 msg = sqlite3_errmsg (conn -> db );
14201533 connection_release_lock (conn );
14211534 } else if (enif_get_resource (env , argv [0 ], statement_type , (void * * )& statement )) {
14221535 statement_acquire_lock (statement );
1536+ if (statement -> statement == NULL ) {
1537+ statement_release_lock (statement );
1538+ return am_nil ;
1539+ }
14231540 msg = sqlite3_errmsg (sqlite3_db_handle (statement -> statement ));
14241541 statement_release_lock (statement );
14251542 } else {
0 commit comments