summaryrefslogtreecommitdiffstats
path: root/web/server/web_client_cache.c
blob: 5aa3af22e90ea87a22304615ff7c242e6ca9600c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// SPDX-License-Identifier: GPL-3.0-or-later

#define WEB_SERVER_INTERNALS 1
#include "web_client_cache.h"

// ----------------------------------------------------------------------------
// allocate and free web_clients

// ----------------------------------------------------------------------------
// web clients caching

// When clients connect and disconnect, avoid allocating and releasing memory.
// Instead, when new clients get connected, reuse any memory previously allocated
// for serving web clients that are now disconnected.

// The size of the cache is adaptive. It caches the structures of 2x
// the number of currently connected clients.

static struct clients_cache {
    struct {
        SPINLOCK spinlock;
        struct web_client *head;    // the structures of the currently connected clients
        size_t count;               // the count the currently connected clients

        size_t allocated;           // the number of allocations
        size_t reused;              // the number of re-uses
    } used;

    struct {
        SPINLOCK spinlock;
        struct web_client *head;    // the cached structures, available for future clients
        size_t count;               // the number of cached structures
    } avail;
} web_clients_cache = {
        .used = {
                .spinlock = NETDATA_SPINLOCK_INITIALIZER,
                .head = NULL,
                .count = 0,
                .reused = 0,
                .allocated = 0,
        },
        .avail = {
                .spinlock = NETDATA_SPINLOCK_INITIALIZER,
                .head = NULL,
                .count = 0,
        },
};

// destroy the cache and free all the memory it uses
void web_client_cache_destroy(void) {
    internal_error(true, "web_client_cache has %zu used and %zu available clients, allocated %zu, reused %zu (hit %zu%%)."
        , web_clients_cache.used.count
        , web_clients_cache.avail.count
        , web_clients_cache.used.allocated
        , web_clients_cache.used.reused
        , (web_clients_cache.used.allocated + web_clients_cache.used.reused)?(web_clients_cache.used.reused * 100 / (web_clients_cache.used.allocated + web_clients_cache.used.reused)):0
        );

    struct web_client *w, *t;

    spinlock_lock(&web_clients_cache.avail.spinlock);
    w = web_clients_cache.avail.head;
    while(w) {
        t = w;
        w = w->cache.next;
        web_client_free(t);
    }
    web_clients_cache.avail.head = NULL;
    web_clients_cache.avail.count = 0;
    spinlock_unlock(&web_clients_cache.avail.spinlock);

// DO NOT FREE THEM IF THEY ARE USED
//    spinlock_lock(&web_clients_cache.used.spinlock);
//    w = web_clients_cache.used.head;
//    while(w) {
//        t = w;
//        w = w->next;
//        web_client_free(t);
//    }
//    web_clients_cache.used.head = NULL;
//    web_clients_cache.used.count = 0;
//    web_clients_cache.used.reused = 0;
//    web_clients_cache.used.allocated = 0;
//    spinlock_unlock(&web_clients_cache.used.spinlock);
}

struct web_client *web_client_get_from_cache(void) {
    spinlock_lock(&web_clients_cache.avail.spinlock);
    struct web_client *w = web_clients_cache.avail.head;
    if(w) {
        // get it from avail
        DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next);
        web_clients_cache.avail.count--;
        spinlock_unlock(&web_clients_cache.avail.spinlock);

        web_client_reuse_from_cache(w);

        spinlock_lock(&web_clients_cache.used.spinlock);
        web_clients_cache.used.reused++;
    }
    else {
        spinlock_unlock(&web_clients_cache.avail.spinlock);

        // allocate it
        w = web_client_create(&netdata_buffers_statistics.buffers_web);

        spinlock_lock(&web_clients_cache.used.spinlock);
        web_clients_cache.used.allocated++;
    }

    // link it to used web clients
    DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next);
    web_clients_cache.used.count++;
    spinlock_unlock(&web_clients_cache.used.spinlock);

    // initialize it
    w->use_count++;
    w->id = global_statistics_web_client_connected();
    w->mode = WEB_CLIENT_MODE_GET;

    return w;
}

void web_client_release_to_cache(struct web_client *w) {

#ifdef ENABLE_HTTPS
    netdata_ssl_close(&w->ssl);
#endif

    // unlink it from the used
    spinlock_lock(&web_clients_cache.used.spinlock);
    DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(web_clients_cache.used.head, w, cache.prev, cache.next);
    ssize_t used_count = (ssize_t)--web_clients_cache.used.count;
    spinlock_unlock(&web_clients_cache.used.spinlock);

    spinlock_lock(&web_clients_cache.avail.spinlock);
    if(w->use_count > 100 || (used_count > 0 && web_clients_cache.avail.count >= 2 * (size_t)used_count) || (used_count <= 10 && web_clients_cache.avail.count >= 20)) {
        spinlock_unlock(&web_clients_cache.avail.spinlock);

        // we have too many of them - free it
        web_client_free(w);
    }
    else {
        // link it to the avail
        DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(web_clients_cache.avail.head, w, cache.prev, cache.next);
        web_clients_cache.avail.count++;
        spinlock_unlock(&web_clients_cache.avail.spinlock);
    }
}