3737#include "sql_cacher.h"
3838#include "../../lib/csv.h"
3939
40- /*
41- * cache_lock conditional compilation of atomic variable support
42- * so we don't need to acquire a lock for reads
43- * if the cache_table param full_caching_lock_scope is set to row
44- * if atomic ops are not supported then the param has no effect
45- */
46- #ifndef NO_ATOMIC_OPS
40+ /* Row-mode writers serialize with each other, but run alongside readers.
41+ * Upgrade to exclusive access only while changing the reload version, state
42+ * or column layout. Readers keep this state stable through fetch + decode.
43+ * This also works on platforms without C11 atomics. */
4744#define lock_cache_writes (cache_lock ) \
4845 do { \
49- atomic_store(&cache_lock.is_writing, 1); \
50- lock_start_write(cache_lock.ref_lock); \
46+ if ((cache_lock).scope == CACHE_LOCK_ROW) \
47+ lock_start_sw_read((cache_lock).ref_lock); \
48+ else \
49+ lock_start_write((cache_lock).ref_lock); \
5150 } while (0)
5251#define unlock_cache_writes (cache_lock ) \
5352 do { \
54- atomic_store(&cache_lock.is_writing, 0); \
55- lock_stop_write(cache_lock.ref_lock); \
53+ if ((cache_lock).scope == CACHE_LOCK_ROW) \
54+ lock_stop_sw_read((cache_lock).ref_lock); \
55+ else \
56+ lock_stop_write((cache_lock).ref_lock); \
5657 } while (0)
57- #define acquire_read_lock (cache_lock ) \
58+ #define lock_cache_state (cache_lock , old ) \
5859 do { \
59- if (cache_lock.scope == CACHE_LOCK_TABLE) { \
60- lock_start_read(cache_lock.ref_lock); \
61- } \
60+ if ((cache_lock).scope == CACHE_LOCK_ROW) \
61+ lock_switch_write((cache_lock).ref_lock, old); \
6262 } while (0)
63- #define release_read_lock (cache_lock ) \
63+ #define unlock_cache_state (cache_lock , old ) \
6464 do { \
65- if (cache_lock.scope == CACHE_LOCK_TABLE) { \
66- lock_stop_read(cache_lock.ref_lock); \
67- } \
65+ if ((cache_lock).scope == CACHE_LOCK_ROW) \
66+ lock_switch_read((cache_lock).ref_lock, old); \
6867 } while (0)
69- #define is_cache_writing (cache_lock ) atomic_load(&cache_lock.is_writing)
70- #define init_cache_lock ((cache_lock_t){NULL, CACHE_LOCK_ROW, 0})
71- #else
72- #define lock_cache_writes (cache_lock ) lock_start_write(cache_lock.ref_lock)
73- #define unlock_cache_writes (cache_lock ) lock_stop_write(cache_lock.ref_lock)
74- #define acquire_read_lock (cache_lock ) lock_start_read(cache_lock.ref_lock)
75- #define release_read_lock (cache_lock ) lock_stop_read(cache_lock.ref_lock)
76- #define is_cache_writing (cache_lock ) 0
77- #define init_cache_lock ((cache_lock_t){NULL, CACHE_LOCK_ROW})
78- #endif
68+ #define acquire_read_lock (cache_lock ) lock_start_read((cache_lock).ref_lock)
69+ #define release_read_lock (cache_lock ) lock_stop_read((cache_lock).ref_lock)
70+
71+ static void finish_cache_reload (cache_lock_t * cache_lock )
72+ {
73+ int old_w_flag = 0 ;
7974
80- /* end of cache_lock conditional compilation */
75+ lock_cache_state (* cache_lock , old_w_flag );
76+ cache_lock -> is_writing = 0 ;
77+ unlock_cache_state (* cache_lock , old_w_flag );
78+ unlock_cache_writes (* cache_lock );
79+ }
8180
8281static int mod_init (void );
8382static void destroy (void );
@@ -239,7 +238,9 @@ static int parse_cache_entry(unsigned int type, void *val)
239238 new_entry -> nr_ints = 0 ;
240239 new_entry -> nr_strs = 0 ;
241240 new_entry -> column_types = 0 ;
242- new_entry -> cache_lock = init_cache_lock ;
241+ new_entry -> cache_lock .ref_lock = NULL ;
242+ new_entry -> cache_lock .scope = CACHE_LOCK_ROW ;
243+ new_entry -> cache_lock .is_writing = 0 ;
243244
244245#define PARSE_TOKEN (_ptr1 , _ptr2 , field , field_name_str , field_name_len ) \
245246 do { \
@@ -462,7 +463,7 @@ static int parse_cache_entry(unsigned int type, void *val)
462463 goto parse_err ;
463464 }
464465 tmp = memchr (p2 + 1 , spec_delimiter .s [0 ],
465- parse_str .len - (p2 - parse_str .s + 1 ));
466+ parse_str .len - (p2 - parse_str .s + 1 ));
466467 str str_val ;
467468 if (!tmp ) /* delimiter not found, reached the end of the string to parse */
468469 str_val .len = parse_str .len - (p2 - parse_str .s + 1 );
@@ -496,7 +497,10 @@ static int parse_cache_entry(unsigned int type, void *val)
496497 if (p2 - p1 == EXPIRE_STR_LEN &&
497498 !memcmp (p1 , EXPIRE_STR , EXPIRE_STR_LEN )) {
498499 str str_val ;
499- str_val .len = parse_str .len - (p2 - parse_str .s + 1 );
500+ tmp = memchr (p2 + 1 , spec_delimiter .s [0 ],
501+ parse_str .len - (p2 - parse_str .s + 1 ));
502+ str_val .len = tmp ? tmp - p2 - 1 :
503+ parse_str .len - (p2 - parse_str .s + 1 );
500504 if (str_val .len <= 0 ) {
501505 LM_ERR ("expected value of: %.*s\n" , EXPIRE_STR_LEN , EXPIRE_STR );
502506 goto parse_err ;
@@ -520,12 +524,8 @@ static int parse_cache_entry(unsigned int type, void *val)
520524 }
521525 }
522526
523- if (!memcmp (p1 , FULL_CACHING_LOCK_SCOPE , FULL_CACHING_LOCK_SCOPE_STR_LEN )) {
524- if (* (p1 + FULL_CACHING_LOCK_SCOPE_STR_LEN ) != '=' ) { \
525- LM_ERR ("expected: '=' after: %.*s\n" , FULL_CACHING_LOCK_SCOPE_STR_LEN , FULL_CACHING_LOCK_SCOPE );
526- goto parse_err ;
527- }
528-
527+ if (p2 - p1 == FULL_CACHING_LOCK_SCOPE_STR_LEN &&
528+ !memcmp (p1 , FULL_CACHING_LOCK_SCOPE , FULL_CACHING_LOCK_SCOPE_STR_LEN )) {
529529 tmp = memchr (p2 + 1 , spec_delimiter .s [0 ],
530530 parse_str .len - (p2 - parse_str .s + 1 ));
531531 if (!tmp )
@@ -543,19 +543,15 @@ static int parse_cache_entry(unsigned int type, void *val)
543543 else if (len == LOCK_SCOPE_TABLE_STR_LEN && !memcmp (p2 + 1 , LOCK_SCOPE_TABLE , len ))
544544 new_entry -> cache_lock .scope = CACHE_LOCK_TABLE ;
545545 else {
546- LM_ERR ("Unsupported cache lock scope '%.*s' defaulting to row\n" , len , p2 + 1 );
546+ LM_ERR ("Unsupported cache lock scope '%.*s' (expected row or table)\n" ,
547+ len , p2 + 1 );
548+ goto parse_err ;
547549 }
548550
549551 if (!tmp ) /* delimiter not found, reached the end of the string to parse */
550552 goto end_parsing ;
551- else {
552- p1 = tmp + 1 ;
553- p2 = memchr (p1 , '=' , parse_str .len - (p1 - parse_str .s ));
554- if (!p2 ) {
555- LM_ERR ("expected: '='\n" );
556- goto parse_err ;
557- }
558- }
553+ LM_ERR ("unexpected parameter after full_caching_lock_scope\n" );
554+ goto parse_err ;
559555 } else if (parse_str .len - (p1 - parse_str .s ) > 0 ) {
560556 LM_ERR ("unknown parameter: %.*s\n" ,
561557 (int )(parse_str .len - (p1 - parse_str .s )), p1 );
@@ -1073,6 +1069,8 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls,
10731069 int i ;
10741070 int reload_vers = 0 ;
10751071 int loaded_rec = 0 ;
1072+ int old_w_flag = 0 ;
1073+ cache_entry_t layout ;
10761074
10771075 sr_add_report ( sql_srg , STR2CI (c_entry -> id ),
10781076 CHAR_INT ("starting DB data loading" ), 0 );
@@ -1128,32 +1126,58 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls,
11281126 pkg_free (query_cols );
11291127
11301128 lock_cache_writes (db_hdls -> c_entry -> cache_lock );
1131-
1132- if (update_cache_rld_vers (db_hdls , inc_rld_vers , & reload_vers ) < 0 ) {
1133- unlock_cache_writes (db_hdls -> c_entry -> cache_lock );
1134- goto error ;
1135- }
1129+ lock_cache_state (c_entry -> cache_lock , old_w_flag );
11361130
11371131 /* anything loaded ? if not, we can do a quick exit here */
11381132 if (RES_ROW_N (sql_res ) == 0 )
1139- goto done ;
1133+ goto update_version ;
11401134
11411135 row = RES_ROWS (sql_res );
11421136 values = ROW_VALUES (row );
1143- if (get_column_types (c_entry , values + 1 , ROW_N (row ) - 1 ) < 0 ) {
1137+ layout = * c_entry ;
1138+ if (get_column_types (& layout , values + 1 , ROW_N (row ) - 1 ) < 0 ) {
1139+ unlock_cache_state (c_entry -> cache_lock , old_w_flag );
11441140 unlock_cache_writes (db_hdls -> c_entry -> cache_lock );
11451141 goto error ;
11461142 }
1143+ /* Old and new rows coexist in row mode, so their layouts must agree. */
1144+ if (c_entry -> cache_lock .scope == CACHE_LOCK_ROW &&
1145+ c_entry -> nr_ints + c_entry -> nr_strs != 0 &&
1146+ (layout .nr_ints != c_entry -> nr_ints ||
1147+ layout .nr_strs != c_entry -> nr_strs ||
1148+ layout .column_types != c_entry -> column_types )) {
1149+ LM_ERR ("Column types changed for cache %.*s; restart OpenSIPS "
1150+ "to load the new layout\n" , c_entry -> id .len , c_entry -> id .s );
1151+ unlock_cache_state (c_entry -> cache_lock , old_w_flag );
1152+ unlock_cache_writes (c_entry -> cache_lock );
1153+ goto error ;
1154+ }
1155+ c_entry -> nr_ints = layout .nr_ints ;
1156+ c_entry -> nr_strs = layout .nr_strs ;
1157+ c_entry -> column_types = layout .column_types ;
1158+
1159+ update_version :
1160+ if (update_cache_rld_vers (db_hdls , inc_rld_vers , & reload_vers ) < 0 ) {
1161+ unlock_cache_state (c_entry -> cache_lock , old_w_flag );
1162+ unlock_cache_writes (c_entry -> cache_lock );
1163+ goto error ;
1164+ }
1165+ /* Startup loads do not increment the version and must not revive rows
1166+ * invalidated by an earlier reload. Single-key reloads never set this. */
1167+ c_entry -> cache_lock .is_writing = inc_rld_vers != 0 && RES_ROW_N (sql_res ) != 0 ;
1168+ unlock_cache_state (c_entry -> cache_lock , old_w_flag );
1169+ if (RES_ROW_N (sql_res ) == 0 )
1170+ goto done ;
11471171
1148- /* load the rows into the cahchedb */
1172+ /* load the rows into the cachedb */
11491173 do {
11501174 for (i = 0 ; i < RES_ROW_N (sql_res ); i ++ ) {
11511175 row = RES_ROWS (sql_res ) + i ;
11521176 values = ROW_VALUES (row );
11531177 if (!VAL_NULL (values )) {
11541178 if (insert_in_cachedb (c_entry , db_hdls , values ,values + 1 ,
11551179 reload_vers , ROW_N (row ) - 1 ) < 0 ) {
1156- unlock_cache_writes ( db_hdls -> c_entry -> cache_lock );
1180+ finish_cache_reload ( & c_entry -> cache_lock );
11571181 goto error ;
11581182 }
11591183 loaded_rec ++ ;
@@ -1164,7 +1188,7 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls,
11641188 if (db_hdls -> db_funcs .fetch_result (db_hdls -> db_con ,& sql_res ,fetch_nr_rows )< 0 ) {
11651189 LM_ERR ("Error fetching rows (1) from SQL DB: %s\n" ,
11661190 db_url_escape (& c_entry -> db_url ));
1167- unlock_cache_writes ( db_hdls -> c_entry -> cache_lock );
1191+ finish_cache_reload ( & c_entry -> cache_lock );
11681192 goto error ;
11691193 }
11701194 } else {
@@ -1173,7 +1197,7 @@ static int load_entire_table(cache_entry_t *c_entry, db_handlers_t *db_hdls,
11731197 } while (RES_ROW_N (sql_res ) > 0 );
11741198
11751199done :
1176- unlock_cache_writes ( db_hdls -> c_entry -> cache_lock );
1200+ finish_cache_reload ( & c_entry -> cache_lock );
11771201
11781202 db_hdls -> db_funcs .free_result (db_hdls -> db_con , sql_res );
11791203
@@ -1215,6 +1239,7 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key,
12151239 db_row_t * row ;
12161240 db_val_t key_val ;
12171241 str src_key , null_val ;
1242+ int rc , old_w_flag = 0 ;
12181243
12191244 src_key .len = c_entry -> id .len + key .len ;
12201245 src_key .s = pkg_malloc (src_key .len );
@@ -1273,9 +1298,16 @@ static int load_key(cache_entry_t *c_entry, db_handlers_t *db_hdls, str key,
12731298 row = RES_ROWS (* sql_res );
12741299 * values = ROW_VALUES (row );
12751300
1276- if (c_entry -> nr_ints + c_entry -> nr_strs == 0 &&
1277- get_column_types (c_entry , * values , ROW_N (row )) < 0 )
1278- goto sql_error ;
1301+ if (c_entry -> nr_ints + c_entry -> nr_strs == 0 ) {
1302+ /* A key reload can precede the initial full-table load. */
1303+ if (!c_entry -> on_demand )
1304+ lock_cache_state (c_entry -> cache_lock , old_w_flag );
1305+ rc = get_column_types (c_entry , * values , ROW_N (row ));
1306+ if (!c_entry -> on_demand )
1307+ unlock_cache_state (c_entry -> cache_lock , old_w_flag );
1308+ if (rc < 0 )
1309+ goto sql_error ;
1310+ }
12791311
12801312 if (insert_in_cachedb (c_entry , db_hdls , & key_val , * values , rld_vers , ROW_N (row )) < 0 )
12811313 goto sql_error ;
@@ -1529,7 +1561,7 @@ static int mod_init(void)
15291561 c_entry -> cache_lock .ref_lock = lock_init_rw ();
15301562 if (!c_entry -> cache_lock .ref_lock ) {
15311563 LM_ERR ("Failed to init readers-writers lock\n" );
1532- continue ;
1564+ return -1 ;
15331565 }
15341566 if (sr_register_identifier ( sql_srg , STR2CI (c_entry -> id ),
15351567 SR_STATUS_NO_DATA , CHAR_INT ("no data loaded" ), 20 ) ) {
@@ -1577,7 +1609,8 @@ static int child_init(int rank)
15771609 }
15781610
15791611 /* perform full caching load in the same process but after child_init is done */
1580- if ((rank == 1 ) && ipc_send_rpc (process_no , cache_init_load , NULL ) < 0 ) {
1612+ if (rank == 1 && db_hdls_list &&
1613+ ipc_send_rpc (process_no , cache_init_load , NULL ) < 0 ) {
15811614 LM_ERR ("Failed to RPC full caching load\n" );
15821615 return -1 ;
15831616 }
@@ -1639,11 +1672,14 @@ static int cdb_val_decode(const pv_name_fix_t *pv_name, const str *cdb_val, int
16391672 goto error ;
16401673 memcpy (& int_val , int_buf , 4 );
16411674
1642- if (reload_version != int_val ) {
1643- if (!is_cache_writing (pv_name -> c_entry -> cache_lock ) ||
1644- (is_cache_writing (pv_name -> c_entry -> cache_lock ) && reload_version != int_val + 1 ))
1645- return 3 ;
1646- }
1675+ /* The caller holds a read lock through fetch + decode. Only a full
1676+ * reload in row mode permits the immediately preceding generation. */
1677+ if (int_val != reload_version &&
1678+ (pv_name -> c_entry -> on_demand ||
1679+ pv_name -> c_entry -> cache_lock .scope != CACHE_LOCK_ROW ||
1680+ !pv_name -> c_entry -> cache_lock .is_writing ||
1681+ reload_version <= 0 || int_val != reload_version - 1 ))
1682+ return 3 ;
16471683
16481684 /* null integer value in db */
16491685 if (!memcmp (cdb_val -> s + pv_name -> col_offset , zeroes , INT_B64_ENC_LEN ))
@@ -2472,3 +2508,7 @@ static int sql_cache_dump(struct sip_msg *msg, db_handlers_t *dbh,
24722508
24732509 return n == 0 ? -2 : n ;
24742510}
2511+
2512+ #ifdef UNIT_TESTS
2513+ #include "test/test_sql_cacher.h"
2514+ #endif
0 commit comments