nginx_shared_memory_lock

Overview

As for shared memory, different processes may allocate memory from it at the same time, so it needs a lock when allocate and free shared memory, nginx provides ngx_shmtx_t which is a combination of spin lock and semaphore, in that lock, first try spin lock, if can’t get the lock, use semaphore to block process.

shared memory lock

Data structure and API

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
/* This part always from shared memory, it is on shared memory */
typedef struct {
ngx_atomic_t lock;
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t wait;
#endif
} ngx_shmtx_sh_t;

/* shmtx is the lock for shared memory
* within atomic(spin lock) if not get the lock, process spins a while, then yield the CPU by way(semaphore)
* blocked until resource is available, when wake up, check resource again to see if can get it
*/

typedef struct {
ngx_atomic_t *lock; /* save the process id who holds the lock(can access the shared memory) */
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t *wait; /* count how many process blocked on this lock */

/* you can see lock and wait are pointers, because they always point to ngx_shmtx_sh_t
* which is allocated from the shared memory, hence every process can see lock and wait
*/
ngx_uint_t semaphore; /* flag that indicates semaphore is enabled or not */
sem_t sem; /* posix semaphore used by sem_init/sem_wait/sem_post */
#endif
/* spin count if we can't get the lock
* as it's unit hence spin = -1 means spin for ever if call ngx_shmtx_lock()
* mostly used for accessing critical area within short period that another process only spin very shortly!!!
*
* Actually: nginx does not spin for ever even if spin = -1, as with spin = -1, nginx does NOT call ngx_shmtx_lock
* but ngx_shmtx_trylock()!!!
*/
ngx_uint_t spin;
} ngx_shmtx_t;

typedef struct {
ngx_shmtx_sh_t lock; /* spin lock used by ngx_shmtx_t */
ngx_shmtx_t mutex;
} ngx_slab_pool_t;

API

1
2
void ngx_shmtx_lock(ngx_shmtx_t *mtx);
void ngx_shmtx_unlock(ngx_shmtx_t *mtx);

inside ngx_shmtx_lock

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
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_uint_t i, n;
for ( ;; ) {

if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
/* get the lock and save the process id */
return;
}

if (ngx_ncpu > 1) {
/* only need to spin if multicore
* if there is only one core, no need to spin, spin here, the lock can't free at all which is hold by other process, spin waste time
* because during the spin, you still can't the lock as the hold process is not running!!!
*/
for (n = 1; n < mtx->spin; n <<= 1) {

for (i = 0; i < n; i++) {
/* use assemble directive to pause cpu */
ngx_cpu_pause();
}

/* spin means cpu pauses a while then check if i can get the lock
* if NO, pause a while again, then check...repeated
*/
if (*mtx->lock == 0
&& ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
{
return;
}
}
}

#if (NGX_HAVE_POSIX_SEM)
/* goes here means after spin a while, we still can't get the lock
* so block my self by semaphore
*/
if (mtx->semaphore) {
/* increase process counter who are waiting on the semaphore */
(void) ngx_atomic_fetch_add(mtx->wait, 1);

/* before call sem_wait to block the process, here let's try one more time
* to see if the lock is freed by another process
*/
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
(void) ngx_atomic_fetch_add(mtx->wait, -1);
return;
}

while (sem_wait(&mtx->sem) == -1) {
/* block here */
ngx_err_t err;

err = ngx_errno;

if (err != NGX_EINTR) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
"sem_wait() failed while waiting on shmtx");
break;
}
}

/* as I'm awoke, decrease the count of waiting processes?
* Yes! but not here as we already did this in wake process when call ngx_shmtx_wakeup
*/
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx awoke");

/* after wake up, start next loop(try to get lock) */
continue;
}

#endif
/* it goes here only when semaphore is unavailable
* yield just call usleep(1) after one spin loop is over to block the process
*/
ngx_sched_yield();
}
}