static void li_memcached_con_free(liMemcachedCon* con) { if (!con) return; if (-1 != li_event_io_fd(&con->con_watcher)) { close(li_event_io_fd(&con->con_watcher)); li_event_clear(&con->con_watcher); con->fd = -1; } send_queue_reset(&con->out); cancel_all_requests(con); li_buffer_release(con->buf); li_buffer_release(con->line); li_buffer_release(con->remaining); li_buffer_release(con->data); reset_item(&con->curitem); li_sockaddr_clear(&con->addr); g_string_free(con->tmpstr, TRUE); g_clear_error(&con->err); g_slice_free(liMemcachedCon, con); }
static void li_memcached_con_free(liMemcachedCon* con) { if (!con) return; if (-1 != con->con_watcher.fd) { close(con->con_watcher.fd); /* as io has a reference on con, we don't need to stop it here */ ev_io_set(&con->con_watcher, -1, 0); con->fd = -1; } send_queue_reset(&con->out); cancel_all_requests(con); li_buffer_release(con->buf); li_buffer_release(con->line); li_buffer_release(con->remaining); li_buffer_release(con->data); reset_item(&con->curitem); li_sockaddr_clear(&con->addr); g_string_free(con->tmpstr, TRUE); g_clear_error(&con->err); g_slice_free(liMemcachedCon, con); }
static ssize_t stream_pushv(gnutls_transport_ptr_t trans, const li_iovec_t * iov, int iovcnt) { const ssize_t blocksize = 16*1024; /* 16k */ liGnuTLSFilter *f = (liGnuTLSFilter*) trans; liChunkQueue *cq; int i; liBuffer *buf; gboolean cq_buf_append; ssize_t written = 0; errno = ECONNRESET; if (NULL == f || NULL == f->crypt_source.out) return -1; cq = f->crypt_source.out; if (cq->is_closed) return -1; buf = f->raw_out_buffer; cq_buf_append = (buf != NULL && buf == li_chunkqueue_get_last_buffer(cq, 1024)); for (i = 0; i < iovcnt; ++i) { const char *data = iov[i].iov_base; size_t len = iov[i].iov_len; while (len > 0) { size_t bufsize, do_write; if (NULL == buf) buf = li_buffer_new(blocksize); bufsize = buf->alloc_size - buf->used; do_write = (bufsize > len) ? len : bufsize; memcpy(buf->addr + buf->used, data, do_write); len -= do_write; data += do_write; if (cq_buf_append) { /* also updates buf->used */ li_chunkqueue_update_last_buffer_size(cq, do_write); } else { gsize offset = buf->used; buf->used += do_write; li_buffer_acquire(buf); li_chunkqueue_append_buffer2(cq, buf, offset, do_write); cq_buf_append = TRUE; } if (buf->used == buf->alloc_size) { li_buffer_release(buf); buf = NULL; cq_buf_append = FALSE; } written += do_write; } } if (NULL != buf && buf->alloc_size - buf->used < 1024) { li_buffer_release(buf); f->raw_out_buffer = buf = NULL; } else { f->raw_out_buffer = buf; } li_stream_notify_later(&f->crypt_source); errno = 0; return written; }
static void send_queue_reset(GQueue *queue) { send_item *i; while (NULL != (i = g_queue_pop_head(queue))) { li_buffer_release(i->buf); g_slice_free(send_item, i); } }
static void f_release(liGnuTLSFilter *f) { LI_FORCE_ASSERT(f->refcount > 0); if (0 == --f->refcount) { f->refcount = 1; f_close_gnutls(f); if (NULL != f->raw_in_buffer) { li_buffer_release(f->raw_in_buffer); f->raw_in_buffer = NULL; } if (NULL != f->raw_out_buffer) { li_buffer_release(f->raw_out_buffer); f->raw_out_buffer = NULL; } g_slice_free(liGnuTLSFilter, f); } }
static void send_queue_clean(GQueue *queue) { send_item *i; while (NULL != (i = g_queue_peek_head(queue))) { if (i->len != 0) return; g_queue_pop_head(queue); li_buffer_release(i->buf); g_slice_free(send_item, i); } }
static void add_remaining(liMemcachedCon *con, gchar *addr, gsize len) { liBuffer *rem = con->remaining; if (!rem) rem = con->remaining = li_buffer_new_slice(MAX(BUFFER_CHUNK_SIZE, len)); if (rem->used + len > rem->alloc_size) { rem = li_buffer_new_slice(MAX(BUFFER_CHUNK_SIZE, rem->used + len)); memcpy(rem->addr, con->remaining->addr, (rem->used = con->remaining->used)); li_buffer_release(con->remaining); con->remaining = rem; } memcpy(rem->addr + rem->used, addr, len); rem->used += len; }
static void reset_item(liMemcachedItem *item) { if (item->key) { g_string_free(item->key, TRUE); item->key = NULL; } item->flags = 0; item->ttl = 0; item->cas = 0; if (item->data) { li_buffer_release(item->data); item->data = NULL; } }
static void memcache_store_filter_free(liVRequest *vr, liFilter *f) { memcache_filter *mf = (memcache_filter*) f->param; UNUSED(vr); if (NULL == f->param) return; f->param = NULL; mc_ctx_release(NULL, mf->ctx); li_buffer_release(mf->buf); mf->buf = NULL; g_slice_free(memcache_filter, mf); }
static liHandlerResult mc_lookup_handle_free(liVRequest *vr, gpointer param, gpointer context) { memcache_request *req = context; UNUSED(vr); UNUSED(param); if (NULL == req->req) { li_buffer_release(req->buffer); g_slice_free(memcache_request, req); } else { req->vr = NULL; } return LI_HANDLER_GO_ON; }
/* pass ownership of one buffer reference to chunkqueue * if the length is NULL, reference is released immediately */ void li_chunkqueue_append_buffer(liChunkQueue *cq, liBuffer *buffer) { liChunk *c; if (!buffer->used) { li_buffer_release(buffer); return; } c = chunk_new(); c->type = BUFFER_CHUNK; c->data.buffer.buffer = buffer; c->data.buffer.offset = 0; c->data.buffer.length = buffer->used; g_queue_push_tail_link(&cq->queue, &c->cq_link); cq->length += buffer->used; cq->bytes_in += buffer->used; cqlimit_update(cq, buffer->used); }
void li_chunkqueue_append_buffer2(liChunkQueue *cq, liBuffer *buffer, gsize offset, gsize length) { liChunk *c; if (length == 0) { li_buffer_release(buffer); return; } assert(offset + length <= buffer->used); c = chunk_new(); c->type = BUFFER_CHUNK; c->data.buffer.buffer = buffer; c->data.buffer.offset = offset; c->data.buffer.length = length; g_queue_push_tail_link(&cq->queue, &c->cq_link); cq->length += length; cq->bytes_in += length; cqlimit_update(cq, length); }
static void send_queue_push_gstring(GQueue *queue, GString *str, liBuffer **pbuf) { liBuffer *buf = *pbuf; gsize pos; if (NULL != buf && (1 == buf->refcount)) { buf->used = 0; } if (NULL == buf || (buf->alloc_size - buf->used < str->len)) { li_buffer_release(buf); buf = li_buffer_new_slice(BUFFER_CHUNK_SIZE > str->len ? BUFFER_CHUNK_SIZE : str->len); *pbuf = buf; } pos = buf->used; memcpy(buf->addr + pos, str->str, str->len); buf->used += str->len; send_queue_push_buffer(queue, buf, pos, str->len); }
static void proxy_connection_free(proxy_connection *pcon) { liVRequest *vr; if (!pcon) return; vr = pcon->vr; ev_io_stop(vr->wrk->loop, &pcon->fd_watcher); proxy_context_release(pcon->ctx); if (pcon->fd != -1) close(pcon->fd); li_vrequest_backend_finished(vr); li_chunkqueue_free(pcon->proxy_in); li_chunkqueue_free(pcon->proxy_out); li_buffer_release(pcon->proxy_in_buffer); li_http_response_parser_clear(&pcon->parse_response_ctx); g_slice_free(proxy_connection, pcon); }
static void fastcgi_connection_free(fastcgi_connection *fcon) { liVRequest *vr; if (!fcon) return; vr = fcon->vr; ev_io_stop(vr->wrk->loop, &fcon->fd_watcher); fastcgi_context_release(fcon->ctx); if (fcon->fd != -1) close(fcon->fd); li_vrequest_backend_finished(vr); li_chunkqueue_free(fcon->fcgi_in); li_chunkqueue_free(fcon->fcgi_out); li_chunkqueue_free(fcon->stdout); li_buffer_release(fcon->fcgi_in_buffer); g_byte_array_free(fcon->buf_in_record, TRUE); li_http_response_parser_clear(&fcon->parse_response_ctx); g_slice_free(fastcgi_connection, fcon); }
static void chunk_free(liChunkQueue *cq, liChunk *c) { if (!c) return; if (cq) { g_queue_unlink(&cq->queue, &c->cq_link); } switch (c->type) { case UNUSED_CHUNK: break; case STRING_CHUNK: g_string_free(c->data.str, TRUE); c->data.str = NULL; break; case MEM_CHUNK: /* mem is handled extra below */ break; case FILE_CHUNK: if (c->data.file.file) { li_chunkfile_release(c->data.file.file); c->data.file.file = NULL; } if (c->data.file.mmap.data != MAP_FAILED) { munmap(c->data.file.mmap.data, c->data.file.mmap.length); c->data.file.mmap.data = MAP_FAILED; } break; case BUFFER_CHUNK: li_buffer_release(c->data.buffer.buffer); break; } c->type = UNUSED_CHUNK; if (c->mem) { g_byte_array_free(c->mem, TRUE); c->mem = NULL; } g_slice_free(liChunk, c); }
static void free_request(liMemcachedCon *con, int_request *req) { if (!req) return; li_memcached_con_release(con); if (NULL != req->iter.data) { req->iter.data = NULL; g_queue_unlink(&con->req_queue, &req->iter); } switch (req->type) { case REQ_GET: break; case REQ_SET: li_buffer_release(req->data); req->data = NULL; break; } g_string_free(req->key, TRUE); req->key = NULL; g_slice_free(int_request, req); }
static liHandlerResult mc_handle_lookup(liVRequest *vr, gpointer param, gpointer *context) { memcached_ctx *ctx = param; memcache_request *req = *context; if (req) { static const GString default_mime_str = { CONST_STR_LEN("application/octet-stream"), 0 }; liBuffer *buf = req->buffer; const GString *mime_str; if (NULL != req->req) return LI_HANDLER_WAIT_FOR_EVENT; /* not done yet */ g_slice_free(memcache_request, req); *context = NULL; if (NULL == buf) { /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } if (!li_vrequest_handle_direct(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } li_buffer_release(buf); return LI_HANDLER_GO_ON; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: key found, handling request"); } li_chunkqueue_append_buffer(vr->direct_out, buf); vr->response.http_status = 200; mime_str = li_mimetype_get(vr, vr->request.uri.path); if (!mime_str) mime_str = &default_mime_str; li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str)); /* hit */ if (ctx->act_found) li_action_enter(vr, ctx->act_found); return LI_HANDLER_GO_ON; } else { liMemcachedCon *con; GError *err = NULL; if (li_vrequest_is_handled(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } return LI_HANDLER_GO_ON; } con = mc_ctx_prepare(ctx, vr->wrk); mc_ctx_build_key(vr->wrk->tmp_str, ctx, vr); if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "memcached.lookup: looking up key '%s'", vr->wrk->tmp_str->str); } req = g_slice_new0(memcache_request); req->req = li_memcached_get(con, vr->wrk->tmp_str, memcache_callback, req, &err); if (NULL == req->req) { if (NULL != err) { if (LI_MEMCACHED_DISABLED != err->code) { VR_ERROR(vr, "memcached.lookup: get failed: %s", err->message); } g_clear_error(&err); } else { VR_ERROR(vr, "memcached.lookup: get failed: %s", "Unkown error"); } g_slice_free(memcache_request, req); /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } req->vr = vr; *context = req; return LI_HANDLER_WAIT_FOR_EVENT; } }
static void do_gnutls_read(liGnuTLSFilter *f) { const ssize_t blocksize = 16*1024; /* 16k */ off_t max_read = 4 * blocksize; /* 64k */ ssize_t r; off_t len = 0; liChunkQueue *cq = f->plain_source.out; f_acquire(f); if (NULL != f->session && !f->initial_handshaked_finished && !do_gnutls_handshake(f, FALSE)) goto out; if (NULL == f->session) { f_abort_gnutls(f); goto out; } do { liBuffer *buf; gboolean cq_buf_append; buf = li_chunkqueue_get_last_buffer(cq, 1024); cq_buf_append = (buf != NULL); if (buf != NULL) { /* use last buffer as raw_in_buffer; they should be the same anyway */ if (G_UNLIKELY(buf != f->raw_in_buffer)) { li_buffer_acquire(buf); li_buffer_release(f->raw_in_buffer); f->raw_in_buffer = buf; } } else { buf = f->raw_in_buffer; if (buf != NULL && buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); f->raw_in_buffer = buf = NULL; } if (buf == NULL) { f->raw_in_buffer = buf = li_buffer_new(blocksize); } } LI_FORCE_ASSERT(f->raw_in_buffer == buf); r = gnutls_record_recv(f->session, buf->addr + buf->used, buf->alloc_size - buf->used); if (r < 0) { do_handle_error(f, "gnutls_record_recv", r, FALSE); goto out; } else if (r == 0) { /* clean shutdown? */ f->plain_source.out->is_closed = TRUE; f->plain_drain.out->is_closed = TRUE; f->crypt_source.out->is_closed = TRUE; f->crypt_drain.out->is_closed = TRUE; li_stream_disconnect(&f->crypt_drain); /* io -> crypt in */ li_stream_disconnect_dest(&f->crypt_source); /* crypt out -> io */ li_stream_disconnect(&f->crypt_source); /* plain in -> crypt out */ f_close_gnutls(f); goto out; } if (cq_buf_append) { li_chunkqueue_update_last_buffer_size(cq, r); } else { gsize offset; li_buffer_acquire(buf); offset = buf->used; buf->used += r; li_chunkqueue_append_buffer2(cq, buf, offset, r); } if (buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); f->raw_in_buffer = buf = NULL; } len += r; } while (len < max_read); out: f_release(f); }
static gboolean try_read_data(liMemcachedCon *con, gsize datalen) { liBuffer *data; ssize_t r; datalen += 2; /* \r\n */ /* if we have remaining data use it for a new line */ if ((!con->data || con->data->used == 0) && con->remaining && con->remaining->used > 0) { liBuffer *tmp = con->remaining; con->remaining = con->data; con->data = tmp; } if (!con->data) con->data = li_buffer_new_slice(MAX(BUFFER_CHUNK_SIZE, datalen)); if (con->data->alloc_size < datalen) { data = li_buffer_new_slice(MAX(BUFFER_CHUNK_SIZE, datalen)); memcpy(data->addr, con->data->addr, (data->used = con->data->used)); li_buffer_release(con->data); con->data = data; } g_assert(NULL == con->remaining || 0 == con->remaining->used); /* there shouldn't be any data in remaining while we fill con->data */ data = con->data; if (data->used < datalen) { /* read more data */ r = net_read(con->fd, data->addr + data->used, data->alloc_size - data->used); if (r == 0) { /* EOF */ g_clear_error(&con->err); g_set_error(&con->err, LI_MEMCACHED_ERROR, LI_MEMCACHED_CONNECTION, "Connection closed by peer"); close_con(con); return FALSE; } else if (r < 0) { switch (errno) { case EAGAIN: #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif break; default: g_clear_error(&con->err); g_set_error(&con->err, LI_MEMCACHED_ERROR, LI_MEMCACHED_CONNECTION, "Connection closed: %s", g_strerror(errno)); close_con(con); break; } return FALSE; } data->used += r; } if (data->used >= datalen) { if (data->addr[datalen-2] != '\r' || data->addr[datalen-1] != '\n') { /* Protocol error: data block not terminated with \r\n */ g_clear_error(&con->err); g_set_error(&con->err, LI_MEMCACHED_ERROR, LI_MEMCACHED_CONNECTION, "Protocol error: data block not terminated with \\r\\n"); close_con(con); return FALSE; } add_remaining(con, data->addr + datalen, data->used - datalen); data->used = datalen - 2; data->addr[datalen-2] = '\0'; return TRUE; } return FALSE; }
liNetworkStatus li_network_read(liVRequest *vr, int fd, liChunkQueue *cq, liBuffer **buffer) { const ssize_t blocksize = 16*1024; /* 16k */ off_t max_read = 16 * blocksize; /* 256k */ ssize_t r; off_t len = 0; if (cq->limit && cq->limit->limit > 0) { if (max_read > cq->limit->limit - cq->limit->current) { max_read = cq->limit->limit - cq->limit->current; if (max_read <= 0) { max_read = 0; /* we still have to read something */ VR_ERROR(vr, "%s", "li_network_read: fd should be disabled as chunkqueue is already full"); } } } do { liBuffer *buf = NULL; gboolean cq_buf_append; buf = li_chunkqueue_get_last_buffer(cq, 1024); cq_buf_append = (buf != NULL); if (NULL != buffer) { if (buf != NULL) { /* use last buffer as *buffer; they should be the same anyway */ if (G_UNLIKELY(buf != *buffer)) { li_buffer_acquire(buf); li_buffer_release(*buffer); *buffer = buf; } } else { buf = *buffer; if (buf != NULL) { /* if *buffer is the only reference, we can reset the buffer */ if (g_atomic_int_get(&buf->refcount) == 1) { buf->used = 0; } if (buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); *buffer = buf = NULL; } } if (buf == NULL) { *buffer = buf = li_buffer_new(blocksize); } } assert(*buffer == buf); } else { if (buf == NULL) { buf = li_buffer_new(blocksize); } } if (-1 == (r = li_net_read(fd, buf->addr + buf->used, buf->alloc_size - buf->used))) { if (buffer == NULL && !cq_buf_append) li_buffer_release(buf); switch (errno) { case EAGAIN: #if EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif return LI_NETWORK_STATUS_WAIT_FOR_EVENT; case ECONNRESET: case ETIMEDOUT: return LI_NETWORK_STATUS_CONNECTION_CLOSE; default: VR_ERROR(vr, "oops, read from fd=%d failed: %s", fd, g_strerror(errno) ); return LI_NETWORK_STATUS_FATAL_ERROR; } } else if (0 == r) { if (buffer == NULL && !cq_buf_append) li_buffer_release(buf); return LI_NETWORK_STATUS_CONNECTION_CLOSE; } if (cq_buf_append) { li_chunkqueue_update_last_buffer_size(cq, r); } else { gsize offset; if (buffer != NULL) li_buffer_acquire(buf); offset = buf->used; buf->used += r; li_chunkqueue_append_buffer2(cq, buf, offset, r); } if (NULL != buffer) { if (buf->alloc_size - buf->used < 1024) { /* release *buffer */ li_buffer_release(buf); *buffer = buf = NULL; } } len += r; } while (r == blocksize && len < max_read); return LI_NETWORK_STATUS_SUCCESS; }
static void send_queue_item_free(send_item *i) { if (!i) return; li_buffer_release(i->buf); g_slice_free(send_item, i); }