]> git.proxmox.com Git - mirror_frr.git/blame - lib/seqlock.c
lib/seqlock: add timed-wait operation
[mirror_frr.git] / lib / seqlock.c
CommitLineData
440d5faa
DL
1/*
2 * "Sequence" lock primitive
3 *
4 * Copyright (C) 2015 David Lamparter <equinox@diac24.net>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA
20 */
21
22#define _GNU_SOURCE
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27
2a5e6235 28#include <string.h>
440d5faa
DL
29#include <unistd.h>
30#include <limits.h>
31#include <errno.h>
32#include <sys/types.h>
33#include <sys/time.h>
34#include <pthread.h>
35#include <assert.h>
36
37#include "seqlock.h"
38
39#ifdef HAVE_SYNC_LINUX_FUTEX
40/* Linux-specific - sys_futex() */
41#include <sys/syscall.h>
42#include <linux/futex.h>
43
2a5e6235
DL
44static long sys_futex(void *addr1, int op, int val1,
45 const struct timespec *timeout, void *addr2, int val3)
440d5faa
DL
46{
47 return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
48}
49
50#define wait_once(sqlo, val) \
51 sys_futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
2a5e6235
DL
52#define wait_time(sqlo, val, time, reltime) \
53 sys_futex((int *)&sqlo->pos, FUTEX_WAIT_BITSET, (int)val, time, \
54 NULL, ~0U)
440d5faa
DL
55#define wait_poke(sqlo) \
56 sys_futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)
57
58#elif defined(HAVE_SYNC_OPENBSD_FUTEX)
2a5e6235 59/* OpenBSD variant of the above. */
440d5faa
DL
60#include <sys/syscall.h>
61#include <sys/futex.h>
62
2a5e6235
DL
63#define TIME_RELATIVE 1
64
440d5faa
DL
65#define wait_once(sqlo, val) \
66 futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, NULL, NULL, 0)
2a5e6235
DL
67#define wait_time(sqlo, val, time, reltime) \
68 futex((int *)&sqlo->pos, FUTEX_WAIT, (int)val, reltime, NULL, 0)
440d5faa
DL
69#define wait_poke(sqlo) \
70 futex((int *)&sqlo->pos, FUTEX_WAKE, INT_MAX, NULL, NULL, 0)
71
72#elif defined(HAVE_SYNC_UMTX_OP)
73/* FreeBSD-specific: umtx_op() */
74#include <sys/umtx.h>
75
76#define wait_once(sqlo, val) \
77 _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val, NULL, NULL)
2a5e6235
DL
78static int wait_time(struct seqlock *sqlo, uint32_t val,
79 const struct timespec *abstime,
80 const struct timespec *reltime)
81{
82 struct _umtx_time t;
83 t._flags = UMTX_ABSTIME;
84 t._clockid = CLOCK_MONOTONIC;
85 memcpy(&t._timeout, abstime, sizeof(t._timeout));
86 return _umtx_op((void *)&sqlo->pos, UMTX_OP_WAIT_UINT, val,
87 (void *)(uintptr_t) sizeof(t), &t);
88}
440d5faa
DL
89#define wait_poke(sqlo) \
90 _umtx_op((void *)&sqlo->pos, UMTX_OP_WAKE, INT_MAX, NULL, NULL)
91
92#else
93/* generic version. used on *BSD, Solaris and OSX.
94 */
95
2a5e6235
DL
96#define TIME_ABS_REALTIME 1
97
440d5faa
DL
98#define wait_init(sqlo) do { \
99 pthread_mutex_init(&sqlo->lock, NULL); \
100 pthread_cond_init(&sqlo->wake, NULL); \
101 } while (0)
102#define wait_prep(sqlo) pthread_mutex_lock(&sqlo->lock)
103#define wait_once(sqlo, val) pthread_cond_wait(&sqlo->wake, &sqlo->lock)
2a5e6235
DL
104#define wait_time(sqlo, val, time, reltime) \
105 pthread_cond_timedwait(&sqlo->wake, \
106 &sqlo->lock, time);
440d5faa
DL
107#define wait_done(sqlo) pthread_mutex_unlock(&sqlo->lock)
108#define wait_poke(sqlo) do { \
109 pthread_mutex_lock(&sqlo->lock); \
110 pthread_cond_broadcast(&sqlo->wake); \
111 pthread_mutex_unlock(&sqlo->lock); \
112 } while (0)
113
114#endif
115
116#ifndef wait_init
117#define wait_init(sqlo) /**/
118#define wait_prep(sqlo) /**/
119#define wait_done(sqlo) /**/
120#endif /* wait_init */
121
122
123void seqlock_wait(struct seqlock *sqlo, seqlock_val_t val)
124{
125 seqlock_val_t cur, cal;
126
127 seqlock_assert_valid(val);
128
129 wait_prep(sqlo);
6046b690
DL
130 cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
131
132 while (cur & SEQLOCK_HELD) {
133 cal = SEQLOCK_VAL(cur) - val - 1;
440d5faa
DL
134 assert(cal < 0x40000000 || cal > 0xc0000000);
135 if (cal < 0x80000000)
136 break;
137
6046b690
DL
138 if ((cur & SEQLOCK_WAITERS)
139 || atomic_compare_exchange_weak_explicit(
140 &sqlo->pos, &cur, cur | SEQLOCK_WAITERS,
141 memory_order_relaxed, memory_order_relaxed)) {
142 wait_once(sqlo, cur | SEQLOCK_WAITERS);
143 cur = atomic_load_explicit(&sqlo->pos,
144 memory_order_relaxed);
145 }
146 /* else: we failed to swap in cur because it just changed */
440d5faa
DL
147 }
148 wait_done(sqlo);
149}
150
2a5e6235
DL
151bool seqlock_timedwait(struct seqlock *sqlo, seqlock_val_t val,
152 const struct timespec *abs_monotime_limit)
153{
154#if TIME_ABS_REALTIME
155#define time_arg1 &abs_rt
156#define time_arg2 NULL
157#define time_prep
158 struct timespec curmono, abs_rt;
159
160 clock_gettime(CLOCK_MONOTONIC, &curmono);
161 clock_gettime(CLOCK_REALTIME, &abs_rt);
162
163 abs_rt.tv_nsec += abs_monotime_limit->tv_nsec - curmono.tv_nsec;
164 if (abs_rt.tv_nsec < 0) {
165 abs_rt.tv_sec--;
166 abs_rt.tv_nsec += 1000000000;
167 } else if (abs_rt.tv_nsec >= 1000000000) {
168 abs_rt.tv_sec++;
169 abs_rt.tv_nsec -= 1000000000;
170 }
171 abs_rt.tv_sec += abs_monotime_limit->tv_sec - curmono.tv_sec;
172
173#elif TIME_RELATIVE
174 struct timespec reltime;
175
176#define time_arg1 abs_monotime_limit
177#define time_arg2 &reltime
178#define time_prep \
179 clock_gettime(CLOCK_MONOTONIC, &reltime); \
180 reltime.tv_sec = abs_monotime_limit.tv_sec - reltime.tv_sec; \
181 reltime.tv_nsec = abs_monotime_limit.tv_nsec - reltime.tv_nsec; \
182 if (reltime.tv_nsec < 0) { \
183 reltime.tv_sec--; \
184 reltime.tv_nsec += 1000000000; \
185 }
186#else
187#define time_arg1 abs_monotime_limit
188#define time_arg2 NULL
189#define time_prep
190#endif
191
192 bool ret = true;
193 seqlock_val_t cur, cal;
194
195 seqlock_assert_valid(val);
196
197 wait_prep(sqlo);
198 cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
199
200 while (cur & SEQLOCK_HELD) {
201 cal = SEQLOCK_VAL(cur) - val - 1;
202 assert(cal < 0x40000000 || cal > 0xc0000000);
203 if (cal < 0x80000000)
204 break;
205
206 if ((cur & SEQLOCK_WAITERS)
207 || atomic_compare_exchange_weak_explicit(
208 &sqlo->pos, &cur, cur | SEQLOCK_WAITERS,
209 memory_order_relaxed, memory_order_relaxed)) {
210 int rv;
211
212 time_prep
213
214 rv = wait_time(sqlo, cur | SEQLOCK_WAITERS, time_arg1,
215 time_arg2);
216 if (rv) {
217 ret = false;
218 break;
219 }
220 cur = atomic_load_explicit(&sqlo->pos,
221 memory_order_relaxed);
222 }
223 }
224 wait_done(sqlo);
225
226 return ret;
227}
228
440d5faa
DL
229bool seqlock_check(struct seqlock *sqlo, seqlock_val_t val)
230{
231 seqlock_val_t cur;
232
233 seqlock_assert_valid(val);
234
6046b690
DL
235 cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
236 if (!(cur & SEQLOCK_HELD))
440d5faa 237 return 1;
6046b690 238 cur = SEQLOCK_VAL(cur) - val - 1;
440d5faa
DL
239 assert(cur < 0x40000000 || cur > 0xc0000000);
240 return cur < 0x80000000;
241}
242
243void seqlock_acquire_val(struct seqlock *sqlo, seqlock_val_t val)
244{
6046b690
DL
245 seqlock_val_t prev;
246
440d5faa
DL
247 seqlock_assert_valid(val);
248
6046b690
DL
249 prev = atomic_exchange_explicit(&sqlo->pos, val, memory_order_relaxed);
250 if (prev & SEQLOCK_WAITERS)
251 wait_poke(sqlo);
440d5faa
DL
252}
253
254void seqlock_release(struct seqlock *sqlo)
255{
6046b690
DL
256 seqlock_val_t prev;
257
258 prev = atomic_exchange_explicit(&sqlo->pos, 0, memory_order_relaxed);
259 if (prev & SEQLOCK_WAITERS)
260 wait_poke(sqlo);
440d5faa
DL
261}
262
263void seqlock_init(struct seqlock *sqlo)
264{
265 sqlo->pos = 0;
266 wait_init(sqlo);
267}
268
269
270seqlock_val_t seqlock_cur(struct seqlock *sqlo)
271{
6046b690
DL
272 return SEQLOCK_VAL(atomic_load_explicit(&sqlo->pos,
273 memory_order_relaxed));
440d5faa
DL
274}
275
276seqlock_val_t seqlock_bump(struct seqlock *sqlo)
277{
6046b690
DL
278 seqlock_val_t val, cur;
279
280 cur = atomic_load_explicit(&sqlo->pos, memory_order_relaxed);
281 seqlock_assert_valid(cur);
282
283 do {
284 val = SEQLOCK_VAL(cur) + SEQLOCK_INCR;
285 } while (!atomic_compare_exchange_weak_explicit(&sqlo->pos, &cur, val,
286 memory_order_relaxed, memory_order_relaxed));
440d5faa 287
6046b690
DL
288 if (cur & SEQLOCK_WAITERS)
289 wait_poke(sqlo);
440d5faa
DL
290 return val;
291}