aboutsummaryrefslogtreecommitdiff
path: root/nginx/ngx_js_shared_dict.c
diff options
context:
space:
mode:
Diffstat (limited to 'nginx/ngx_js_shared_dict.c')
-rw-r--r--nginx/ngx_js_shared_dict.c1146
1 files changed, 1129 insertions, 17 deletions
diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c
index 0a35241f..5445b4f2 100644
--- a/nginx/ngx_js_shared_dict.c
+++ b/nginx/ngx_js_shared_dict.c
@@ -18,6 +18,9 @@ typedef struct {
ngx_rbtree_t rbtree_expire;
ngx_rbtree_node_t sentinel_expire;
+
+ unsigned dirty:1;
+ unsigned writing:1;
} ngx_js_dict_sh_t;
@@ -26,12 +29,23 @@ struct ngx_js_dict_s {
ngx_js_dict_sh_t *sh;
ngx_slab_pool_t *shpool;
+ /**
+ * in order for ngx_js_dict_t to be used as a ngx_event_t data,
+ * fd is used for event debug and should be at the same position
+ * as in ngx_connection_t. see ngx_event_ident() for details.
+ */
+ ngx_socket_t fd;
+
ngx_msec_t timeout;
ngx_flag_t evict;
#define NGX_JS_DICT_TYPE_STRING 0
#define NGX_JS_DICT_TYPE_NUMBER 1
ngx_uint_t type;
+ ngx_event_t save_event;
+ ngx_str_t state_file;
+ ngx_str_t state_temp_file;
+
ngx_js_dict_t *next;
};
@@ -49,6 +63,13 @@ typedef struct {
} ngx_js_dict_node_t;
+typedef struct {
+ ngx_str_t key;
+ ngx_js_dict_value_t value;
+ ngx_msec_t expire;
+} ngx_js_dict_entry_t;
+
+
static njs_int_t njs_js_ext_shared_dict_capacity(njs_vm_t *vm,
njs_object_prop_t *prop, uint32_t unused, njs_value_t *value,
njs_value_t *setval, njs_value_t *retval);
@@ -625,8 +646,14 @@ njs_js_ext_shared_dict_clear(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
done:
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
njs_value_undefined_set(retval);
return NJS_OK;
@@ -1246,6 +1273,16 @@ njs_js_ext_shared_dict_type(njs_vm_t *vm, njs_object_prop_t *prop,
}
+static njs_int_t
+ngx_js_dict_shared_error_name(njs_vm_t *vm, njs_object_prop_t *prop,
+ uint32_t unused, njs_value_t *value, njs_value_t *setval,
+ njs_value_t *retval)
+{
+ return njs_vm_value_string_create(vm, retval,
+ (u_char *) "SharedMemoryError", 17);
+}
+
+
static ngx_js_dict_node_t *
ngx_js_dict_lookup(ngx_js_dict_t *dict, ngx_str_t *key)
{
@@ -1329,8 +1366,14 @@ ngx_js_dict_set(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key,
}
}
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return NGX_OK;
memory_error:
@@ -1494,8 +1537,14 @@ ngx_js_dict_delete(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key,
ngx_js_dict_node_free(dict, node);
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return rc;
}
@@ -1536,8 +1585,14 @@ ngx_js_dict_incr(njs_vm_t *vm, ngx_js_dict_t *dict, ngx_str_t *key,
}
}
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return NGX_OK;
}
@@ -1676,13 +1731,1013 @@ ngx_js_dict_evict(ngx_js_dict_t *dict, ngx_int_t count)
}
-static njs_int_t
-ngx_js_dict_shared_error_name(njs_vm_t *vm, njs_object_prop_t *prop,
- uint32_t unused, njs_value_t *value, njs_value_t *setval,
- njs_value_t *retval)
+static ngx_int_t
+ngx_js_render_string(njs_chb_t *chain, ngx_str_t *str)
{
- return njs_vm_value_string_create(vm, retval,
- (u_char *) "SharedMemoryError", 17);
+ size_t size;
+ u_char c, *dst, *dst_end;
+ const u_char *p, *end;
+
+ static char hex2char[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ p = str->data;
+ end = p + str->len;
+ size = str->len + 2;
+
+ dst = njs_chb_reserve(chain, size);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ dst_end = dst + size;
+
+ *dst++ = '\"';
+ njs_chb_written(chain, 1);
+
+ while (p < end) {
+ if (dst_end <= dst + sizeof("\\uXXXX")) {
+ size = ngx_max(end - p + 1, 6);
+ dst = njs_chb_reserve(chain, size);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ dst_end = dst + size;
+ }
+
+ if (*p < ' ' || *p == '\\' || *p == '\"') {
+ c = (u_char) *p++;
+ *dst++ = '\\';
+ njs_chb_written(chain, 2);
+
+ switch (c) {
+ case '\\':
+ *dst++ = '\\';
+ break;
+ case '"':
+ *dst++ = '\"';
+ break;
+ case '\r':
+ *dst++ = 'r';
+ break;
+ case '\n':
+ *dst++ = 'n';
+ break;
+ case '\t':
+ *dst++ = 't';
+ break;
+ case '\b':
+ *dst++ = 'b';
+ break;
+ case '\f':
+ *dst++ = 'f';
+ break;
+ default:
+ *dst++ = 'u';
+ *dst++ = '0';
+ *dst++ = '0';
+ *dst++ = hex2char[(c & 0xf0) >> 4];
+ *dst++ = hex2char[c & 0x0f];
+ njs_chb_written(chain, 4);
+ }
+
+ continue;
+ }
+
+ dst = njs_utf8_copy(dst, &p, end);
+
+ njs_chb_written(chain, dst - chain->last->pos);
+ }
+
+ njs_chb_append_literal(chain, "\"");
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_js_dict_render_json(ngx_js_dict_t *dict, njs_chb_t *chain)
+{
+ u_char *p, *dst;
+ size_t len;
+ ngx_msec_t now;
+ ngx_time_t *tp;
+ ngx_rbtree_t *rbtree;
+ ngx_rbtree_node_t *rn, *next;
+ ngx_js_dict_node_t *node;
+
+ tp = ngx_timeofday();
+ now = tp->sec * 1000 + tp->msec;
+
+ rbtree = &dict->sh->rbtree;
+
+ njs_chb_append_literal(chain,"{");
+
+ if (rbtree->root == rbtree->sentinel) {
+ njs_chb_append_literal(chain, "}");
+ return NGX_OK;
+ }
+
+ for (rn = ngx_rbtree_min(rbtree->root, rbtree->sentinel);
+ rn != NULL;
+ rn = next)
+ {
+ node = (ngx_js_dict_node_t *) rn;
+
+ next = ngx_rbtree_next(rbtree, rn);
+
+ if (dict->timeout && now >= node->expire.key) {
+ continue;
+ }
+
+ if (ngx_js_render_string(chain, &node->sn.str) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ njs_chb_append_literal(chain,":{");
+
+ if (dict->type == NGX_JS_DICT_TYPE_STRING) {
+ njs_chb_append_literal(chain,"\"value\":");
+
+ if (ngx_js_render_string(chain, &node->value.str) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ } else {
+ len = sizeof("\"value\":.") + 18 + 6;
+ dst = njs_chb_reserve(chain, len);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = njs_sprintf(dst, dst + len, "\"value\":%.6f",
+ node->value.number);
+ njs_chb_written(chain, p - dst);
+ }
+
+ if (dict->timeout) {
+ len = sizeof(",\"expire\":1000000000");
+ dst = njs_chb_reserve(chain, len);
+ if (dst == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = njs_sprintf(dst, dst + len, ",\"expire\":%ui",
+ node->expire.key);
+ njs_chb_written(chain, p - dst);
+ }
+
+ njs_chb_append_literal(chain, "}");
+
+ if (next != NULL) {
+ njs_chb_append_literal(chain, ",");
+ }
+ }
+
+ njs_chb_append_literal(chain, "}");
+
+ return NGX_OK;
+}
+
+
+static u_char *
+ngx_js_skip_space(u_char *start, u_char *end)
+{
+ u_char *p;
+
+ for (p = start; p != end; p++) {
+
+ switch (*p) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ continue;
+ }
+
+ break;
+ }
+
+ return p;
+}
+
+
+static uint32_t
+ngx_js_unicode(const u_char *p)
+{
+ u_char c;
+ uint32_t utf;
+ njs_uint_t i;
+
+ utf = 0;
+
+ for (i = 0; i < 4; i++) {
+ utf <<= 4;
+ c = p[i] | 0x20;
+ c -= '0';
+ if (c > 9) {
+ c += '0' - 'a' + 10;
+ }
+
+ utf |= c;
+ }
+
+ return utf;
+}
+
+
+static u_char *
+ngx_js_dict_parse_string(ngx_pool_t *pool, u_char *p, u_char *end,
+ ngx_str_t *str, const char **err, u_char **at)
+{
+ u_char ch, *s, *dst, *start, *last;
+ size_t size, surplus;
+ uint32_t utf, utf_low;
+
+ enum {
+ sw_usual = 0,
+ sw_escape,
+ sw_encoded1,
+ sw_encoded2,
+ sw_encoded3,
+ sw_encoded4,
+ } state;
+
+ if (*p != '"') {
+ *err = "unexpected character, expected '\"'";
+ goto error;
+ }
+
+ start = p + 1;
+
+ dst = NULL;
+ state = 0;
+ surplus = 0;
+
+ for (p = start; p < end; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ case sw_usual:
+
+ if (ch == '"') {
+ break;
+ }
+
+ if (ch == '\\') {
+ state = sw_escape;
+ continue;
+ }
+
+ if (ch >= ' ') {
+ continue;
+ }
+
+ *err = "Invalid source char";
+ goto error;
+
+ case sw_escape:
+
+ switch (ch) {
+ case '"':
+ case '\\':
+ case '/':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'b':
+ case 'f':
+ surplus++;
+ state = sw_usual;
+ continue;
+
+ case 'u':
+ /*
+ * Basic unicode 6 bytes "\uXXXX" in JSON
+ * and up to 3 bytes in UTF-8.
+ *
+ * Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON
+ * and 3 or 4 bytes in UTF-8.
+ */
+ surplus += 3;
+ state = sw_encoded1;
+ continue;
+ }
+
+ *err = "Invalid escape char";
+ goto error;
+
+ case sw_encoded1:
+ case sw_encoded2:
+ case sw_encoded3:
+ case sw_encoded4:
+
+ if ((ch >= '0' && ch <= '9')
+ || (ch >= 'A' && ch <= 'F')
+ || (ch >= 'a' && ch <= 'f'))
+ {
+ state = (state == sw_encoded4) ? sw_usual : state + 1;
+ continue;
+ }
+
+ *err = "Invalid Unicode escape sequence";
+ goto error;
+ }
+
+ break;
+ }
+
+ if (p == end) {
+ *err = "unexpected end of input";
+ goto error;
+ }
+
+ /* Points to the ending quote mark. */
+ last = p;
+
+ size = last - start - surplus;
+
+ if (surplus != 0) {
+ p = start;
+
+ dst = ngx_palloc(pool, size);
+ if (dst == NULL) {
+ *err = "out of memory";
+ goto error;
+ }
+
+ s = dst;
+
+ do {
+ ch = *p++;
+
+ if (ch != '\\') {
+ *s++ = ch;
+ continue;
+ }
+
+ ch = *p++;
+
+ switch (ch) {
+ case '"':
+ case '\\':
+ case '/':
+ *s++ = ch;
+ continue;
+
+ case 'n':
+ *s++ = '\n';
+ continue;
+
+ case 'r':
+ *s++ = '\r';
+ continue;
+
+ case 't':
+ *s++ = '\t';
+ continue;
+
+ case 'b':
+ *s++ = '\b';
+ continue;
+
+ case 'f':
+ *s++ = '\f';
+ continue;
+ }
+
+ /* "\uXXXX": Unicode escape sequence. */
+
+ utf = ngx_js_unicode(p);
+ p += 4;
+
+ if (njs_surrogate_any(utf)) {
+
+ if (utf > 0xdbff || p[0] != '\\' || p[1] != 'u') {
+ s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT);
+ continue;
+ }
+
+ p += 2;
+
+ utf_low = ngx_js_unicode(p);
+ p += 4;
+
+ if (njs_fast_path(njs_surrogate_trailing(utf_low))) {
+ utf = njs_surrogate_pair(utf, utf_low);
+
+ } else if (njs_surrogate_leading(utf_low)) {
+ utf = NJS_UNICODE_REPLACEMENT;
+ s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT);
+
+ } else {
+ utf = utf_low;
+ s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT);
+ }
+ }
+
+ s = njs_utf8_encode(s, utf);
+
+ } while (p != last);
+
+ size = s - dst;
+ start = dst;
+ }
+
+ str->data = start;
+ str->len = size;
+
+ return p + 1;
+
+error:
+
+ *at = p;
+
+ return NULL;
+}
+
+
+static u_char *
+ngx_js_dict_parse_entry(ngx_js_dict_t *dict, ngx_pool_t *pool,
+ ngx_js_dict_entry_t *entry, u_char *buf, u_char *end, const char **err,
+ u_char **at)
+{
+ int see_value;
+ u_char *p, *pp;
+ double number;
+ ngx_str_t key, str;
+
+ p = buf;
+
+ if (*p++ != '{') {
+ *err = "unexpected character, expected '{'";
+ goto error;
+ }
+
+ see_value = 0;
+
+ while (1) {
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ *err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p == '}') {
+ break;
+ }
+
+ p = ngx_js_dict_parse_string(pool, p, end, &key, err, at);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ *err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p++ != ':') {
+ *err = "unexpected character, expected ':'";
+ goto error;
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ *err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p == '\"') {
+ p = ngx_js_dict_parse_string(pool, p, end, &str, err, at);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ if (key.len == 5 && ngx_strncmp(key.data, "value", 5) == 0) {
+ if (dict->type != NGX_JS_DICT_TYPE_STRING) {
+ *err = "expected string value";
+ goto error;
+ }
+
+ entry->value.str = str;
+ see_value = 1;
+ }
+
+ } else {
+ pp = p;
+ number = strtod((char *) p, (char **) &p);
+ if (pp == p) {
+ *err = "invalid number value";
+ goto error;
+ }
+
+ if (key.len == 5 && ngx_strncmp(key.data, "value", 5) == 0) {
+ if (dict->type == NGX_JS_DICT_TYPE_STRING) {
+ *err = "expected number value";
+ goto error;
+ }
+
+ entry->value.number = number;
+ see_value = 1;
+
+ } else if (key.len == 6
+ && ngx_strncmp(key.data, "expire", 6) == 0)
+ {
+ entry->expire = number;
+ }
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ *err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p == ',') {
+ p++;
+ }
+ }
+
+ if (!see_value) {
+ *err = "missing value";
+ goto error;
+ }
+
+ return p + 1;
+
+error:
+
+ *at = p;
+
+ return NULL;
+}
+
+
+static ngx_int_t
+ngx_js_dict_parse_state(ngx_js_dict_t *dict, ngx_pool_t *pool,
+ ngx_array_t *entries, u_char *buf, u_char *end)
+{
+ u_char *p, *at;
+ const char *err;
+ ngx_js_dict_entry_t *e;
+
+ /* GCC complains about uninitialized err, at. */
+
+ err = "";
+ at = NULL;
+
+ p = ngx_js_skip_space(buf, end);
+ if (p == end) {
+ err = "empty json";
+ goto error;
+ }
+
+ if (*p++ != '{') {
+ err = "json must start with '{'";
+ goto error;
+ }
+
+ while (1) {
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p == '}') {
+ p++;
+ break;
+ }
+
+ e = ngx_array_push(entries);
+ if (e == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_js_dict_parse_string(pool, p, end, &e->key, &err, &at);
+ if (p == NULL) {
+ p = at;
+ goto error;
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p++ != ':') {
+ err = "unexpected character, expected ':'";
+ goto error;
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ err = "unexpected end of json";
+ goto error;
+ }
+
+ p = ngx_js_dict_parse_entry(dict, pool, e, p, end, &err, &at);
+ if (p == NULL) {
+ p = at;
+ goto error;
+ }
+
+ p = ngx_js_skip_space(p, end);
+ if (p == end) {
+ err = "unexpected end of json";
+ goto error;
+ }
+
+ if (*p == ',') {
+ p++;
+ }
+ }
+
+ p = ngx_js_skip_space(p, end);
+
+ if (p != end) {
+ err = "unexpected character, expected end of json";
+ goto error;
+ }
+
+ return NGX_OK;
+
+error:
+
+ ngx_log_error(NGX_LOG_EMERG, dict->shm_zone->shm.log, 0,
+ "invalid format while loading js_shared_dict_zone \"%V\""
+ " from state file \"%s\": %s at offset %z",
+ &dict->shm_zone->shm.name, dict->state_file.data, err,
+ p - buf);
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_js_dict_save(ngx_js_dict_t *dict)
+{
+
+ u_char *name;
+ ngx_int_t rc;
+ ngx_log_t *log;
+ njs_chb_t chain;
+ ngx_file_t file;
+ ngx_pool_t *pool;
+ ngx_chain_t *out, *cl, **ll;
+ njs_chb_node_t *node;
+ ngx_ext_rename_file_t ext;
+
+ log = dict->shm_zone->shm.log;
+
+ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log);
+ if (pool == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_rwlock_wlock(&dict->sh->rwlock);
+
+ if (!dict->sh->dirty) {
+ ngx_rwlock_unlock(&dict->sh->rwlock);
+ ngx_destroy_pool(pool);
+ return NGX_OK;
+ }
+
+ if (dict->sh->writing) {
+ ngx_rwlock_unlock(&dict->sh->rwlock);
+ ngx_destroy_pool(pool);
+ return NGX_AGAIN;
+ }
+
+ ngx_rwlock_downgrade(&dict->sh->rwlock);
+
+ NGX_CHB_CTX_INIT(&chain, pool);
+
+ rc = ngx_js_dict_render_json(dict, &chain);
+
+ if (rc != NGX_OK) {
+ ngx_rwlock_unlock(&dict->sh->rwlock);
+ ngx_destroy_pool(pool);
+ return rc;
+ }
+
+ dict->sh->writing = 1;
+ dict->sh->dirty = 0;
+
+ ngx_rwlock_unlock(&dict->sh->rwlock);
+
+ name = dict->state_temp_file.data;
+
+ out = NULL;
+ ll = &out;
+
+ for (node = chain.nodes; node != NULL; node = node->next) {
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ goto error;
+ }
+
+ cl->buf = ngx_calloc_buf(pool);
+ if (cl->buf == NULL) {
+ goto error;
+ }
+
+ cl->buf->pos = node->start;
+ cl->buf->last = node->pos;
+ cl->buf->memory = 1;
+ cl->buf->last_buf = (node->next == NULL) ? 1 : 0;
+
+ *ll = cl;
+ ll = &cl->next;
+ }
+
+ *ll = NULL;
+
+ ngx_memzero(&file, sizeof(ngx_file_t));
+ file.name = dict->state_temp_file;
+ file.log = log;
+
+ file.fd = ngx_open_file(file.name.data, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,
+ NGX_FILE_DEFAULT_ACCESS);
+
+ if (file.fd == NGX_INVALID_FILE) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_open_file_n " \"%s\" failed", name);
+ goto error;
+ }
+
+ rc = ngx_write_chain_to_file(&file, out, 0, pool);
+
+ if (rc == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_write_fd_n " \"%s\" failed", file.name.data);
+ goto error;
+ }
+
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", file.name.data);
+ }
+
+ file.fd = NGX_INVALID_FILE;
+
+ ext.access = 0;
+ ext.time = -1;
+ ext.create_path = 0;
+ ext.delete_file = 0;
+ ext.log = log;
+
+ if (ngx_ext_rename_file(&dict->state_temp_file, &dict->state_file, &ext)
+ != NGX_OK)
+ {
+ goto error;
+ }
+
+ /* no lock required */
+ dict->sh->writing = 0;
+ ngx_destroy_pool(pool);
+
+ return NGX_OK;
+
+error:
+
+ if (file.fd != NGX_INVALID_FILE
+ && ngx_close_file(file.fd) == NGX_FILE_ERROR)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name);
+ }
+
+ ngx_destroy_pool(pool);
+
+ /* no lock required */
+ dict->sh->writing = 0;
+ dict->sh->dirty = 1;
+
+ return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_js_dict_load(ngx_js_dict_t *dict)
+{
+ off_t size;
+ u_char *name, *buf;
+ size_t len;
+ ssize_t n;
+ ngx_fd_t fd;
+ ngx_err_t err;
+ ngx_int_t rc;
+ ngx_log_t *log;
+ ngx_uint_t i;
+ ngx_msec_t now, expire;
+ ngx_time_t *tp;
+ ngx_pool_t *pool;
+ ngx_array_t data;
+ ngx_file_info_t fi;
+ ngx_js_dict_entry_t *entries;
+
+ if (dict->state_file.data == NULL) {
+ return NGX_OK;
+ }
+
+ log = dict->shm_zone->shm.log;
+
+ name = dict->state_file.data;
+
+ fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
+ if (fd == NGX_INVALID_FILE) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT || err == NGX_ENOPATH) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, log, err,
+ ngx_open_file_n " \"%s\" failed", name);
+ return NGX_ERROR;
+ }
+
+ if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ ngx_fd_info_n " \"%s\" failed", name);
+ pool = NULL;
+ goto failed;
+ }
+
+ size = ngx_file_size(&fi);
+
+ if (size == 0) {
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name);
+ }
+
+ return NGX_OK;
+ }
+
+ pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log);
+ if (pool == NULL) {
+ goto failed;
+ }
+
+ len = size;
+
+ buf = ngx_pnalloc(pool, len);
+ if (buf == NULL) {
+ goto failed;
+ }
+
+ n = ngx_read_fd(fd, buf, len);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ ngx_read_fd_n " \"%s\" failed", name);
+ goto failed;
+ }
+
+ if ((size_t) n != len) {
+ ngx_log_error(NGX_LOG_EMERG, log, 0,
+ ngx_read_fd_n " has read only %z of %uz from %s",
+ n, len, name);
+ goto failed;
+ }
+
+ if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name);
+ fd = NGX_INVALID_FILE;
+ goto failed;
+ }
+
+ fd = NGX_INVALID_FILE;
+
+ if (ngx_array_init(&data, pool, 4, sizeof(ngx_js_dict_entry_t))
+ != NGX_OK)
+ {
+ goto failed;
+ }
+
+ rc = ngx_js_dict_parse_state(dict, pool, &data, buf, buf + len);
+
+ if (rc != NGX_OK) {
+ goto failed;
+ }
+
+ entries = data.elts;
+
+ tp = ngx_timeofday();
+ now = tp->sec * 1000 + tp->msec;
+
+ for (i = 0; i < data.nelts; i++) {
+
+ if (dict->timeout) {
+ expire = entries[i].expire;
+
+ if (expire && now >= expire) {
+ dict->sh->dirty = 1;
+ continue;
+ }
+
+ if (expire == 0) {
+ /* treat state without expire as new */
+ expire = now + dict->timeout;
+ dict->sh->dirty = 1;
+ }
+
+ } else {
+ expire = 0;
+ }
+
+ if (ngx_js_dict_lookup(dict, &entries[i].key) != NULL) {
+ goto failed;
+ }
+
+ if (ngx_js_dict_add_value(dict, &entries[i].key, &entries[i].value,
+ expire, 1)
+ != NGX_OK)
+ {
+ goto failed;
+ }
+ }
+
+ ngx_destroy_pool(pool);
+
+ return NGX_OK;
+
+failed:
+
+ if (fd != NGX_INVALID_FILE && ngx_close_file(fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", name);
+ }
+
+ if (pool) {
+ ngx_destroy_pool(pool);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_js_dict_save_handler(ngx_event_t *ev)
+{
+ ngx_int_t rc;
+ ngx_js_dict_t *dict;
+
+ dict = ev->data;
+
+ rc = ngx_js_dict_save(dict);
+
+ if (rc == NGX_OK) {
+ return;
+ }
+
+ if (rc == NGX_ERROR && (ngx_terminate || ngx_exiting)) {
+ ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+ "failed to save the state of shared dict zone \"%V\"",
+ &dict->shm_zone->shm.name);
+ return;
+ }
+
+ /* NGX_ERROR, NGX_AGAIN */
+
+ ngx_add_timer(ev, 1000);
+}
+
+
+ngx_int_t
+ngx_js_dict_init_worker(ngx_js_main_conf_t *jmcf)
+{
+ ngx_js_dict_t *dict;
+
+ if ((ngx_process != NGX_PROCESS_WORKER || ngx_worker != 0)
+ && ngx_process != NGX_PROCESS_SINGLE)
+ {
+ return NGX_OK;
+ }
+
+ if (jmcf->dicts == NULL) {
+ return NGX_OK;
+ }
+
+ for (dict = jmcf->dicts; dict != NULL; dict = dict->next) {
+
+ if (!dict->sh->dirty || !dict->state_file.data) {
+ continue;
+ }
+
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
+ return NGX_OK;
}
@@ -1752,6 +2807,10 @@ ngx_js_dict_init_zone(ngx_shm_zone_t *shm_zone, void *data)
ngx_sprintf(dict->shpool->log_ctx, " in js shared zone \"%V\"%Z",
&shm_zone->shm.name);
+ if (ngx_js_dict_load(dict) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
return NGX_OK;
}
@@ -1764,7 +2823,7 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
u_char *p;
ssize_t size;
- ngx_str_t *value, name, s;
+ ngx_str_t *value, name, file, s;
ngx_flag_t evict;
ngx_msec_t timeout;
ngx_uint_t i, type;
@@ -1775,6 +2834,7 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
evict = 0;
timeout = 0;
name.len = 0;
+ ngx_str_null(&file);
type = NGX_JS_DICT_TYPE_STRING;
value = cf->args->elts;
@@ -1826,6 +2886,17 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
continue;
}
+ if (ngx_strncmp(value[i].data, "state=", 6) == 0) {
+ file.data = value[i].data + 6;
+ file.len = value[i].len - 6;
+
+ if (ngx_conf_full_name(cf->cycle, &file, 0) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
if (ngx_strncmp(value[i].data, "timeout=", 8) == 0) {
s.data = value[i].data + 8;
@@ -1899,6 +2970,23 @@ ngx_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf,
dict->timeout = timeout;
dict->type = type;
+ dict->save_event.handler = ngx_js_dict_save_handler;
+ dict->save_event.data = dict;
+ dict->save_event.log = &cf->cycle->new_log;
+ dict->fd = -1;
+
+ if (file.data) {
+ dict->state_file = file;
+
+ p = ngx_pnalloc(cf->pool, file.len + sizeof(".tmp"));
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ dict->state_temp_file.data = p;
+ dict->state_temp_file.len = ngx_sprintf(p, "%V.tmp%Z", &file) - p - 1;
+ }
+
return NGX_CONF_OK;
}
@@ -2098,8 +3186,14 @@ ngx_qjs_ext_shared_dict_clear(JSContext *cx, JSValueConst this_val,
done:
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return JS_UNDEFINED;
}
@@ -2746,8 +3840,14 @@ ngx_qjs_dict_delete(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key,
ngx_js_dict_node_free(dict, node);
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return ret;
}
@@ -2825,8 +3925,14 @@ ngx_qjs_dict_incr(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key,
}
}
+ dict->sh->dirty = 1;
+
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return value;
}
@@ -2856,24 +3962,30 @@ ngx_qjs_dict_set(JSContext *cx, ngx_js_dict_t *dict, ngx_str_t *key,
goto memory_error;
}
- ngx_rwlock_unlock(&dict->sh->rwlock);
+ } else {
- return JS_TRUE;
- }
+ if (flags & NGX_JS_DICT_FLAG_MUST_NOT_EXIST) {
+ if (!dict->timeout || now < node->expire.key) {
+ ngx_rwlock_unlock(&dict->sh->rwlock);
+ return JS_FALSE;
+ }
+ }
- if (flags & NGX_JS_DICT_FLAG_MUST_NOT_EXIST) {
- if (!dict->timeout || now < node->expire.key) {
- ngx_rwlock_unlock(&dict->sh->rwlock);
- return JS_FALSE;
+ if (ngx_qjs_dict_update(cx, dict, node, value, timeout, now)
+ != NGX_OK)
+ {
+ goto memory_error;
}
}
- if (ngx_qjs_dict_update(cx, dict, node, value, timeout, now) != NGX_OK) {
- goto memory_error;
- }
+ dict->sh->dirty = 1;
ngx_rwlock_unlock(&dict->sh->rwlock);
+ if (dict->state_file.data && !dict->save_event.timer_set) {
+ ngx_add_timer(&dict->save_event, 1000);
+ }
+
return JS_TRUE;
memory_error: