ntdb: next-generation trivial key-value database
[ccan] / ccan / ntdb / test / api-locktimeout.c
1 #include "config.h"
2 #include "ntdb.h"
3 #include "private.h"
4 #include "tap-interface.h"
5 #include <limits.h>
6 #include "logging.h"
7 #include "external-agent.h"
8
9 #undef alarm
10 #define alarm fast_alarm
11
12 /* Speed things up by doing things in milliseconds. */
13 static unsigned int fast_alarm(unsigned int milli_seconds)
14 {
15         struct itimerval it;
16
17         it.it_interval.tv_sec = it.it_interval.tv_usec = 0;
18         it.it_value.tv_sec = milli_seconds / 1000;
19         it.it_value.tv_usec = milli_seconds * 1000;
20         setitimer(ITIMER_REAL, &it, NULL);
21         return 0;
22 }
23
24 #define CatchSignal(sig, handler) signal((sig), (handler))
25
26 static void do_nothing(int signum)
27 {
28 }
29
30 /* This example code is taken from SAMBA, so try not to change it. */
31 static struct flock flock_struct;
32
33 /* Return a value which is none of v1, v2 or v3. */
34 static inline short int invalid_value(short int v1, short int v2, short int v3)
35 {
36         short int try = (v1+v2+v3)^((v1+v2+v3) << 16);
37         while (try == v1 || try == v2 || try == v3)
38                 try++;
39         return try;
40 }
41
42 /* We invalidate in as many ways as we can, so the OS rejects it */
43 static void invalidate_flock_struct(int signum)
44 {
45         flock_struct.l_type = invalid_value(F_RDLCK, F_WRLCK, F_UNLCK);
46         flock_struct.l_whence = invalid_value(SEEK_SET, SEEK_CUR, SEEK_END);
47         flock_struct.l_start = -1;
48         /* A large negative. */
49         flock_struct.l_len = (((off_t)1 << (sizeof(off_t)*CHAR_BIT - 1)) + 1);
50 }
51
52 static int timeout_lock(int fd, int rw, off_t off, off_t len, bool waitflag,
53                         void *_timeout)
54 {
55         int ret, saved_errno = errno;
56         unsigned int timeout = *(unsigned int *)_timeout;
57
58         flock_struct.l_type = rw;
59         flock_struct.l_whence = SEEK_SET;
60         flock_struct.l_start = off;
61         flock_struct.l_len = len;
62
63         CatchSignal(SIGALRM, invalidate_flock_struct);
64         alarm(timeout);
65
66         for (;;) {
67                 if (waitflag)
68                         ret = fcntl(fd, F_SETLKW, &flock_struct);
69                 else
70                         ret = fcntl(fd, F_SETLK, &flock_struct);
71
72                 if (ret == 0)
73                         break;
74
75                 /* Not signalled?  Something else went wrong. */
76                 if (flock_struct.l_len == len) {
77                         if (errno == EAGAIN || errno == EINTR)
78                                 continue;
79                         saved_errno = errno;
80                         break;
81                 } else {
82                         saved_errno = EINTR;
83                         break;
84                 }
85         }
86
87         alarm(0);
88         errno = saved_errno;
89         return ret;
90 }
91
92 static int ntdb_chainlock_with_timeout_internal(struct ntdb_context *ntdb,
93                                                NTDB_DATA key,
94                                                unsigned int timeout,
95                                                int rw_type)
96 {
97         union ntdb_attribute locking;
98         enum NTDB_ERROR ecode;
99
100         if (timeout) {
101                 locking.base.attr = NTDB_ATTRIBUTE_FLOCK;
102                 ecode = ntdb_get_attribute(ntdb, &locking);
103                 if (ecode != NTDB_SUCCESS)
104                         return ecode;
105
106                 /* Replace locking function with our own. */
107                 locking.flock.data = &timeout;
108                 locking.flock.lock = timeout_lock;
109
110                 ecode = ntdb_set_attribute(ntdb, &locking);
111                 if (ecode != NTDB_SUCCESS)
112                         return ecode;
113         }
114         if (rw_type == F_RDLCK)
115                 ecode = ntdb_chainlock_read(ntdb, key);
116         else
117                 ecode = ntdb_chainlock(ntdb, key);
118
119         if (timeout) {
120                 ntdb_unset_attribute(ntdb, NTDB_ATTRIBUTE_FLOCK);
121         }
122         return ecode;
123 }
124
125 int main(int argc, char *argv[])
126 {
127         unsigned int i;
128         struct ntdb_context *ntdb;
129         NTDB_DATA key = ntdb_mkdata("hello", 5);
130         int flags[] = { NTDB_DEFAULT, NTDB_NOMMAP,
131                         NTDB_CONVERT, NTDB_NOMMAP|NTDB_CONVERT };
132         struct agent *agent;
133
134         plan_tests(sizeof(flags) / sizeof(flags[0]) * 15);
135
136         agent = prepare_external_agent();
137
138         for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
139                 enum NTDB_ERROR ecode;
140                 ntdb = ntdb_open("run-locktimeout.ntdb",
141                                  flags[i]|MAYBE_NOSYNC,
142                                  O_RDWR|O_CREAT|O_TRUNC, 0600, &tap_log_attr);
143                 if (!ok1(ntdb))
144                         break;
145
146                 /* Simple cases: should succeed. */
147                 ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
148                                                             F_RDLCK);
149                 ok1(ecode == NTDB_SUCCESS);
150                 ok1(tap_log_messages == 0);
151
152                 ntdb_chainunlock_read(ntdb, key);
153                 ok1(tap_log_messages == 0);
154
155                 ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
156                                                             F_WRLCK);
157                 ok1(ecode == NTDB_SUCCESS);
158                 ok1(tap_log_messages == 0);
159
160                 ntdb_chainunlock(ntdb, key);
161                 ok1(tap_log_messages == 0);
162
163                 /* OK, get agent to start transaction, then we should time out. */
164                 ok1(external_agent_operation(agent, OPEN, "run-locktimeout.ntdb")
165                     == SUCCESS);
166                 ok1(external_agent_operation(agent, TRANSACTION_START, "")
167                     == SUCCESS);
168                 ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
169                                                             F_WRLCK);
170                 ok1(ecode == NTDB_ERR_LOCK);
171                 ok1(tap_log_messages == 0);
172
173                 /* Even if we get a different signal, should be fine. */
174                 CatchSignal(SIGUSR1, do_nothing);
175                 external_agent_operation(agent, SEND_SIGNAL, "");
176                 ecode = ntdb_chainlock_with_timeout_internal(ntdb, key, 20,
177                                                             F_WRLCK);
178                 ok1(ecode == NTDB_ERR_LOCK);
179                 ok1(tap_log_messages == 0);
180
181                 ok1(external_agent_operation(agent, TRANSACTION_COMMIT, "")
182                     == SUCCESS);
183                 ok1(external_agent_operation(agent, CLOSE, "")
184                     == SUCCESS);
185                 ntdb_close(ntdb);
186         }
187         free_external_agent(agent);
188         return exit_status();
189 }