cornflake 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +24 -0
- data/Rakefile +8 -0
- data/bin/Makefile +6 -0
- data/bin/race.sh +20 -0
- data/cornflake.gemspec +17 -0
- data/ext/cornflake/cornflake.c +387 -0
- data/ext/cornflake/cornflake.h +20 -0
- data/ext/cornflake/cornflake.rb.c +91 -0
- data/ext/cornflake/extconf.rb +4 -0
- data/lib/cornflake.rb +26 -0
- data/nginx/README.md +28 -0
- data/nginx/cornflake.c +387 -0
- data/nginx/cornflake.h +20 -0
- data/nginx/ngx_cornflake_module.c +80 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjAyM2YwYzNkY2RiMDlmOTZkNjVkZTZiYmE1MWRlYjBjNzM2ODAyYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjhkNjRhZTYxYjNhY2IxZjZlNTdmZmZlYTUyY2FlZmE0NGZiMzc5Ng==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZDRlNjJhMWQ2ODY1YTgyNTQ4ZGUxNTBmOTVjMjlmZjZjYjkxN2ZjZjI4NThk
|
10
|
+
ZWIxYzY1MGY1YWU0YzczMjM1NTFhMjQzYjhlYThhMWQ4ZGMyZjYzNjAxY2Zj
|
11
|
+
NTA5ZGRjZTdlY2M4ZTVmZmRlMThmMDAyOTlkZjUwMzk1Mjk3YTk=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MmIzNDEzNjkxNWFmMTZmMDQxMjU1MzU1YjUwZDI3Mzk3NGY1YmFmMTNkNjJl
|
14
|
+
YjNkNjM5OTMwMDkwM2Q5ZWUxMDkzNjQxMjIxMDRhZDE3N2FmM2E5NzE5Mzkw
|
15
|
+
YWEwNzIxN2ZjMWEyMzk2MDYxMzFmNTk0YzVjZjA4MzhkNTA2ODg=
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cornflake (0.0.1)
|
5
|
+
macaddr (~> 1.6)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
macaddr (1.6.1)
|
11
|
+
systemu (~> 2.5.0)
|
12
|
+
minitest (5.0.6)
|
13
|
+
rake (10.1.0)
|
14
|
+
rake-compiler (0.9.1)
|
15
|
+
rake
|
16
|
+
systemu (2.5.2)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
cornflake!
|
23
|
+
minitest
|
24
|
+
rake-compiler
|
data/Rakefile
ADDED
data/bin/Makefile
ADDED
data/bin/race.sh
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
for ((i=0;i<15;i++))
|
4
|
+
do
|
5
|
+
./cornflake -n50000 > out.$i.log &
|
6
|
+
done
|
7
|
+
|
8
|
+
wait
|
9
|
+
|
10
|
+
rm -f out.uniq
|
11
|
+
|
12
|
+
for ((i=0;i<15;i++))
|
13
|
+
do
|
14
|
+
cat out.$i.log >> out.uniq.log
|
15
|
+
done
|
16
|
+
|
17
|
+
wc -l out.uniq.log
|
18
|
+
sort out.uniq.log | uniq -d
|
19
|
+
rm -rf out.*.log
|
20
|
+
|
data/cornflake.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'cornflake'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.authors = ["Vicent Martí"]
|
5
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
6
|
+
s.email = %q{vmg@github.com}
|
7
|
+
s.extensions = ["ext/cornflake/extconf.rb"]
|
8
|
+
s.files = `git ls-files`.split("\n")
|
9
|
+
s.homepage = %q{http://github.com/github/cornflake}
|
10
|
+
s.require_paths = ["lib"]
|
11
|
+
s.rubygems_version = %q{1.4.2}
|
12
|
+
s.summary = %q{K-ordered id generation, brought to you by Unix (TM)}
|
13
|
+
s.description = "This does the thing with the ID generation and all that"
|
14
|
+
s.license = "MIT"
|
15
|
+
|
16
|
+
s.add_dependency "macaddr", "~> 1.6"
|
17
|
+
end
|
@@ -0,0 +1,387 @@
|
|
1
|
+
#include <sys/types.h>
|
2
|
+
#include <sys/stat.h>
|
3
|
+
#include <sys/mman.h>
|
4
|
+
#include <fcntl.h>
|
5
|
+
#include <pthread.h>
|
6
|
+
#include <stdint.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <sys/time.h>
|
9
|
+
#include <string.h>
|
10
|
+
#include <unistd.h>
|
11
|
+
#include <errno.h>
|
12
|
+
#include <assert.h>
|
13
|
+
|
14
|
+
#include "cornflake.h"
|
15
|
+
|
16
|
+
#define cpu_relax() __asm__ volatile("pause\n": : :"memory")
|
17
|
+
|
18
|
+
typedef uint32_t spinlock_t;
|
19
|
+
|
20
|
+
static inline int spin_trylock(spinlock_t *lock)
|
21
|
+
{
|
22
|
+
size_t tries = 1000;
|
23
|
+
|
24
|
+
while (tries--) {
|
25
|
+
if (!__sync_lock_test_and_set(lock, 1))
|
26
|
+
return 0;
|
27
|
+
|
28
|
+
while (*lock) {
|
29
|
+
cpu_relax();
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
return -1;
|
34
|
+
}
|
35
|
+
|
36
|
+
static inline int spin_tryunlock(spinlock_t *lock)
|
37
|
+
{
|
38
|
+
if (__sync_lock_test_and_set(lock, 0) == 0)
|
39
|
+
return -1;
|
40
|
+
|
41
|
+
return 0;
|
42
|
+
}
|
43
|
+
|
44
|
+
static inline void spin_unlock(spinlock_t *lock)
|
45
|
+
{
|
46
|
+
__sync_lock_release(lock);
|
47
|
+
}
|
48
|
+
|
49
|
+
struct cornflake {
|
50
|
+
spinlock_t lock;
|
51
|
+
uint64_t last_time;
|
52
|
+
uint16_t counter;
|
53
|
+
};
|
54
|
+
|
55
|
+
static uint8_t g_local_mac[6];
|
56
|
+
|
57
|
+
static struct cornflake *
|
58
|
+
cornflake_open(const char *path)
|
59
|
+
{
|
60
|
+
int fd;
|
61
|
+
struct cornflake *corn;
|
62
|
+
|
63
|
+
fd = open(path, O_RDWR, 0666);
|
64
|
+
if (fd < 0)
|
65
|
+
return NULL;
|
66
|
+
|
67
|
+
corn = mmap(NULL, sizeof(*corn), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
68
|
+
close(fd);
|
69
|
+
|
70
|
+
return corn;
|
71
|
+
}
|
72
|
+
|
73
|
+
static struct cornflake *
|
74
|
+
cornflake_create(const char *path)
|
75
|
+
{
|
76
|
+
int fd;
|
77
|
+
struct cornflake *corn;
|
78
|
+
|
79
|
+
fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0666);
|
80
|
+
if (fd < 0) {
|
81
|
+
if (errno == EEXIST)
|
82
|
+
return cornflake_open(path);
|
83
|
+
|
84
|
+
return NULL;
|
85
|
+
}
|
86
|
+
|
87
|
+
if (ftruncate(fd, sizeof(*corn)) < 0) {
|
88
|
+
close(fd);
|
89
|
+
return NULL;
|
90
|
+
}
|
91
|
+
|
92
|
+
corn = mmap(NULL, sizeof(*corn), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
93
|
+
close(fd);
|
94
|
+
|
95
|
+
corn->lock = 0;
|
96
|
+
corn->last_time = 0;
|
97
|
+
corn->counter = 0;
|
98
|
+
|
99
|
+
return corn;
|
100
|
+
}
|
101
|
+
|
102
|
+
struct cornflake *
|
103
|
+
cornflake_new(const char *path)
|
104
|
+
{
|
105
|
+
struct cornflake *corn;
|
106
|
+
|
107
|
+
corn = cornflake_open(path);
|
108
|
+
if (corn)
|
109
|
+
return corn;
|
110
|
+
|
111
|
+
return cornflake_create(path);
|
112
|
+
}
|
113
|
+
|
114
|
+
void
|
115
|
+
cornflake_close(struct cornflake *corn)
|
116
|
+
{
|
117
|
+
munmap(corn, sizeof(*corn));
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Generate a 16-byte UID. The composition of the UID is as follows.
|
122
|
+
*
|
123
|
+
* [ 8 bytes .......][ 6 bytes ...][2 bytes]
|
124
|
+
* timestamp macaddr counter
|
125
|
+
*
|
126
|
+
* Where:
|
127
|
+
*
|
128
|
+
* - `timestamp` is the current time (in ms since the epoch) when the
|
129
|
+
* UID was generated
|
130
|
+
*
|
131
|
+
* - `macaddr` is the MAC address for this computer, in raw form
|
132
|
+
*
|
133
|
+
* - `counter` is a 16-bit counter to avoid collisions between UIDs
|
134
|
+
* generated in the same milisecond. This limits Cornflake to only
|
135
|
+
* 2^16 UID/s per milisecond.
|
136
|
+
*
|
137
|
+
* If successful, the `uid` buffer will be filled with the unique ID
|
138
|
+
* in raw form. Ensure that the buffer has 16 bytes before calling this
|
139
|
+
* function.
|
140
|
+
*
|
141
|
+
* Return values:
|
142
|
+
*
|
143
|
+
* - CORNFLAKE_OK (0): the UID was generated successfully
|
144
|
+
*
|
145
|
+
* - CORNFLAKE_ELOCK: there was an error when trying to lock the global
|
146
|
+
* state for UID generation. This is most likely because another process
|
147
|
+
* crashed while keeping the lock. As part of the error handling mechanism,
|
148
|
+
* we will free the global lock, so further calls to `cornflake_gen` should
|
149
|
+
* be able to work.
|
150
|
+
*
|
151
|
+
* - CORNFLAKE_EDRIFT: the system clock has drifted into the past. Cornflake
|
152
|
+
* will refuse to generate new UIDs until the clock reaches the time when the
|
153
|
+
* last UID was generated, as to ensure that no UIDs are tagged in the past.
|
154
|
+
*
|
155
|
+
* - CORNFLAKE_EOVERFLOW: the counter in the UID has overflowed (there have been
|
156
|
+
* more than 2^16 unique IDs generated in this milisecond). You need to wait at
|
157
|
+
* least 1 milisecond and try again.
|
158
|
+
*/
|
159
|
+
int
|
160
|
+
cornflake_gen(uint8_t *uid, struct cornflake *corn)
|
161
|
+
{
|
162
|
+
struct timeval tv;
|
163
|
+
uint64_t timestamp;
|
164
|
+
uint16_t sequence = 0;
|
165
|
+
uint8_t *bytes;
|
166
|
+
|
167
|
+
/*
|
168
|
+
* We need to synchronize the global state so only *one* Cornflake
|
169
|
+
* process generates UIDs simultaneously.
|
170
|
+
*
|
171
|
+
* We use a spinlock in shared memory (this requires x86/x64 semantics,
|
172
|
+
* do not try this at home, children, specially if you have an ARM
|
173
|
+
* processor at home).
|
174
|
+
*
|
175
|
+
* There is a caveat when locking this global state: there's an infintesimal
|
176
|
+
* chance that another Cornflake process got terminated (i.e. segmentation
|
177
|
+
* fault, uncaught signal) during the ~20 instructions that happen inside
|
178
|
+
* this lock, before the lock has been released.
|
179
|
+
*
|
180
|
+
* This would usually cause every other process in the system to deadlock
|
181
|
+
* while waiting for the spinlock. To work around this, we use a custom
|
182
|
+
* `trylock` the spinlock: we're going to try a thousand (1000) long locks
|
183
|
+
* in a row (i.e. try the exchange, if failed, loop until the lock is free,
|
184
|
+
* try the exchange again).
|
185
|
+
*
|
186
|
+
* If we haven't acquired a lock after 1000 tries, we'll assume the spinlock
|
187
|
+
* has deadlocked and we'll simply drop it and fail fast so other processes
|
188
|
+
* can continue UID generations. This all should happen in less than ~500ms
|
189
|
+
* even in the highest contention cases.
|
190
|
+
*
|
191
|
+
* FAQ: Why are you using an `xchg` spinlock instead of e.g. `pthread_mutex` on
|
192
|
+
* shared memory or `pthread_spinlock`? All the `pthread` synchronization
|
193
|
+
* primitives have undefined behavior when trying to unlock/destroy them from
|
194
|
+
* a different thread than the one who originally acquired the lock. By controlling
|
195
|
+
* the spinlock semantics, we can ensure the lock gets released when a process dies
|
196
|
+
* holding it *and* we can verify that other processes do not race on the released
|
197
|
+
* lock.
|
198
|
+
*/
|
199
|
+
if (spin_trylock(&corn->lock) < 0) {
|
200
|
+
spin_unlock(&corn->lock);
|
201
|
+
return CORNFLAKE_ELOCK;
|
202
|
+
}
|
203
|
+
|
204
|
+
/*
|
205
|
+
* Get the current time in ms -- this will be the timestamp that shows
|
206
|
+
* on the first 8 bytes of the UID
|
207
|
+
*/
|
208
|
+
gettimeofday(&tv, NULL);
|
209
|
+
timestamp = ((uint64_t)tv.tv_sec * 1000) +
|
210
|
+
((uint64_t)tv.tv_usec / 1000);
|
211
|
+
|
212
|
+
/*
|
213
|
+
* If the current timestamp is behind the timestamp of the last UID we've
|
214
|
+
* generated, that means our system clock has drifted. We cannot generate
|
215
|
+
* new UIDs until the clock is back on time.
|
216
|
+
*/
|
217
|
+
if (timestamp < corn->last_time) {
|
218
|
+
spin_unlock(&corn->lock);
|
219
|
+
return CORNFLAKE_EDRIFT;
|
220
|
+
}
|
221
|
+
|
222
|
+
/*
|
223
|
+
* If the current timestamp is the same as the last UID's we've generated,
|
224
|
+
* we need to increase the UID counter (last 16 bits of the UID) to prevent
|
225
|
+
* collisions.
|
226
|
+
*
|
227
|
+
* When the UID counter wraps around (i.e. reaches 0), we've generated more
|
228
|
+
* than 2^16 UIDs in the millisecond, so we need to wait to the next ms
|
229
|
+
* in order to prevent collisions.
|
230
|
+
*/
|
231
|
+
if (timestamp == corn->last_time) {
|
232
|
+
sequence = ++corn->counter;
|
233
|
+
if (!sequence) {
|
234
|
+
spin_unlock(&corn->lock);
|
235
|
+
return CORNFLAKE_EOVERFLOW;
|
236
|
+
}
|
237
|
+
} else {
|
238
|
+
corn->last_time = timestamp;
|
239
|
+
corn->counter = 0;
|
240
|
+
}
|
241
|
+
|
242
|
+
/*
|
243
|
+
* We're out of the locking zone. HOWEVER: we need to ensure that the lock
|
244
|
+
* we've acquired is still acquired when we unlock it. If somehow the lock
|
245
|
+
* is not taken when we try to unlock it, that means another process has
|
246
|
+
* decided that we're "dead" (we took too long to generate this UID!) and
|
247
|
+
* unlocked the lock for us.
|
248
|
+
*
|
249
|
+
* In that case, we cannot continue: the UID we've just generated could very
|
250
|
+
* well be racy.
|
251
|
+
*/
|
252
|
+
if (spin_tryunlock(&corn->lock) < 0)
|
253
|
+
return CORNFLAKE_ELOCK;
|
254
|
+
|
255
|
+
/*
|
256
|
+
* Move over all the generated data to network byte order (big endian).
|
257
|
+
* This is to ensure that the UIDs are k-ordered when compared
|
258
|
+
* lexicographically.
|
259
|
+
*/
|
260
|
+
bytes = (uint8_t *)×tamp;
|
261
|
+
uid[0] = bytes[7];
|
262
|
+
uid[1] = bytes[6];
|
263
|
+
uid[2] = bytes[5];
|
264
|
+
uid[3] = bytes[4];
|
265
|
+
uid[4] = bytes[3];
|
266
|
+
uid[5] = bytes[2];
|
267
|
+
uid[6] = bytes[1];
|
268
|
+
uid[7] = bytes[0];
|
269
|
+
|
270
|
+
memcpy(&uid[8], g_local_mac, sizeof(g_local_mac));
|
271
|
+
|
272
|
+
bytes = (uint8_t *)&sequence;
|
273
|
+
uid[14] = bytes[1];
|
274
|
+
uid[15] = bytes[0];
|
275
|
+
|
276
|
+
return 0;
|
277
|
+
}
|
278
|
+
|
279
|
+
int
|
280
|
+
cornflake_gen_hex(char *hex_uid, size_t len, struct cornflake *corn)
|
281
|
+
{
|
282
|
+
static char to_hex[] = "0123456789abcdef";
|
283
|
+
|
284
|
+
uint8_t uid[16];
|
285
|
+
int i, ret;
|
286
|
+
|
287
|
+
if (len < 33)
|
288
|
+
return CORNFLAKE_EOVERFLOW;
|
289
|
+
|
290
|
+
ret = cornflake_gen(uid, corn);
|
291
|
+
if (ret < 0)
|
292
|
+
return ret;
|
293
|
+
|
294
|
+
for (i = 0; i < 16; ++i) {
|
295
|
+
*hex_uid++ = to_hex[uid[i] >> 4];
|
296
|
+
*hex_uid++ = to_hex[uid[i] & 0xf];
|
297
|
+
}
|
298
|
+
|
299
|
+
*hex_uid = '\0';
|
300
|
+
return 0;
|
301
|
+
}
|
302
|
+
|
303
|
+
int
|
304
|
+
cornflake_set_mac(const char *mac_addr)
|
305
|
+
{
|
306
|
+
if (strlen(mac_addr) != 17)
|
307
|
+
return CORNFLAKE_EMACADDR;
|
308
|
+
|
309
|
+
if (sscanf(mac_addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
310
|
+
&g_local_mac[0], &g_local_mac[1], &g_local_mac[2],
|
311
|
+
&g_local_mac[3], &g_local_mac[4], &g_local_mac[5]) != 6)
|
312
|
+
return CORNFLAKE_EMACADDR;
|
313
|
+
|
314
|
+
return 0;
|
315
|
+
}
|
316
|
+
|
317
|
+
#ifdef CORNFLAKE_STANDALONE
|
318
|
+
#include "getopt.h"
|
319
|
+
|
320
|
+
static inline double bench_time(void)
|
321
|
+
{
|
322
|
+
struct timespec ts;
|
323
|
+
clock_gettime(CLOCK_REALTIME, &ts);
|
324
|
+
return (ts.tv_sec * 1000.0) + (ts.tv_nsec / 1000000.0);
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
int main(int argc, char *argv[])
|
329
|
+
{
|
330
|
+
static struct option longopts[] = {
|
331
|
+
{ "count", required_argument, NULL, 'n' },
|
332
|
+
{ "path", required_argument, NULL, 'o' },
|
333
|
+
{ "time", no_argument, NULL, 't' },
|
334
|
+
{ "version", no_argument, NULL, 'v' },
|
335
|
+
{ NULL, 0, NULL, 0 }
|
336
|
+
};
|
337
|
+
|
338
|
+
struct cornflake *flake;
|
339
|
+
const char *flake_path = "./milkbowl";
|
340
|
+
int opt, benchmark = 0;
|
341
|
+
unsigned int i, count = 1;
|
342
|
+
double start, end;
|
343
|
+
|
344
|
+
while ((opt = getopt_long(argc, argv, ":n:o:tv", longopts, NULL)) != -1) {
|
345
|
+
switch (opt) {
|
346
|
+
case 'n': count = atoi(optarg); break;
|
347
|
+
case 'o': flake_path = optarg; break;
|
348
|
+
case 't': benchmark = 1; break;
|
349
|
+
case 'v':
|
350
|
+
puts("cornflake");
|
351
|
+
return 0;
|
352
|
+
|
353
|
+
default:
|
354
|
+
printf("Usage: %s [--path CORNFLAKE_CONTEXT] [-n COUNT]\n", argv[0]);
|
355
|
+
return 1;
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
flake = cornflake_new(flake_path);
|
360
|
+
if (flake == NULL) {
|
361
|
+
fprintf(stderr, "Failed to open Cornflake context: %s\n", flake_path);
|
362
|
+
return -1;
|
363
|
+
}
|
364
|
+
|
365
|
+
start = bench_time();
|
366
|
+
|
367
|
+
for (i = 0; i < count; ++i) {
|
368
|
+
char uid[33];
|
369
|
+
int err = cornflake_gen_hex(uid, sizeof(uid), flake);
|
370
|
+
|
371
|
+
if (err < 0) {
|
372
|
+
fprintf(stderr, "Generation failed: %d\n", err);
|
373
|
+
return -2;
|
374
|
+
}
|
375
|
+
|
376
|
+
puts(uid);
|
377
|
+
}
|
378
|
+
|
379
|
+
end = bench_time();
|
380
|
+
|
381
|
+
if (benchmark)
|
382
|
+
fprintf(stderr, "Benchmark: %d oids in %fms\n", count, end - start);
|
383
|
+
|
384
|
+
cornflake_close(flake);
|
385
|
+
return 0;
|
386
|
+
}
|
387
|
+
#endif
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef __CORNFLAKE_H__
|
2
|
+
#define __CORNFLAKE_H__
|
3
|
+
|
4
|
+
struct cornflake;
|
5
|
+
|
6
|
+
enum cornflake_err {
|
7
|
+
CORNFLAKE_OK = 0,
|
8
|
+
CORNFLAKE_ELOCK = -1,
|
9
|
+
CORNFLAKE_EDRIFT = -2,
|
10
|
+
CORNFLAKE_EMACADDR = -3,
|
11
|
+
CORNFLAKE_EOVERFLOW = -4,
|
12
|
+
};
|
13
|
+
|
14
|
+
struct cornflake *cornflake_new(const char *path);
|
15
|
+
void cornflake_close(struct cornflake *corn);
|
16
|
+
int cornflake_gen(uint8_t *uid_out, struct cornflake *corn);
|
17
|
+
int cornflake_gen_hex(char *hex_uid, size_t len, struct cornflake *corn);
|
18
|
+
int cornflake_set_mac(const char *mac_addr);
|
19
|
+
|
20
|
+
#endif
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "cornflake.h"
|
3
|
+
|
4
|
+
static VALUE rb_cCornflake;
|
5
|
+
|
6
|
+
static void
|
7
|
+
rb_cornflake__free(void *corn)
|
8
|
+
{
|
9
|
+
cornflake_close(corn);
|
10
|
+
}
|
11
|
+
|
12
|
+
static VALUE
|
13
|
+
rb_cornflake__new(VALUE klass, VALUE rb_path)
|
14
|
+
{
|
15
|
+
struct cornflake *corn;
|
16
|
+
|
17
|
+
Check_Type(rb_path, T_STRING);
|
18
|
+
|
19
|
+
corn = cornflake_new(StringValueCStr(rb_path));
|
20
|
+
if (!corn)
|
21
|
+
rb_raise(rb_eRuntimeError, "Failed to open Cornflake state at %s",
|
22
|
+
StringValueCStr(rb_path));
|
23
|
+
|
24
|
+
return Data_Wrap_Struct(klass, NULL, rb_cornflake__free, corn);
|
25
|
+
}
|
26
|
+
|
27
|
+
static void
|
28
|
+
rb_cornflake_check(int result)
|
29
|
+
{
|
30
|
+
switch (result) {
|
31
|
+
case CORNFLAKE_OK:
|
32
|
+
return;
|
33
|
+
|
34
|
+
case CORNFLAKE_ELOCK:
|
35
|
+
rb_raise(rb_eRuntimeError, "Failed to lock the global Cornflake state");
|
36
|
+
break;
|
37
|
+
|
38
|
+
case CORNFLAKE_EDRIFT:
|
39
|
+
rb_raise(rb_eRuntimeError, "Computer time drifted into the past");
|
40
|
+
break;
|
41
|
+
|
42
|
+
default:
|
43
|
+
rb_raise(rb_eRuntimeError, "Failed to generate UID");
|
44
|
+
break;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
static VALUE
|
49
|
+
rb_cornflake_gen(VALUE self)
|
50
|
+
{
|
51
|
+
uint8_t uid[16];
|
52
|
+
struct cornflake *corn;
|
53
|
+
|
54
|
+
Data_Get_Struct(self, struct cornflake, corn);
|
55
|
+
rb_cornflake_check(cornflake_gen(uid, corn));
|
56
|
+
|
57
|
+
return rb_str_new((char *)uid, 16);
|
58
|
+
}
|
59
|
+
|
60
|
+
static VALUE
|
61
|
+
rb_cornflake_gen_hex(VALUE self)
|
62
|
+
{
|
63
|
+
char hex_uid[33];
|
64
|
+
struct cornflake *corn;
|
65
|
+
|
66
|
+
Data_Get_Struct(self, struct cornflake, corn);
|
67
|
+
rb_cornflake_check(cornflake_gen_hex(hex_uid, sizeof(hex_uid), corn));
|
68
|
+
|
69
|
+
return rb_str_new2(hex_uid);
|
70
|
+
}
|
71
|
+
|
72
|
+
static VALUE
|
73
|
+
rb_cornflake_set_mac(VALUE klass, VALUE rb_mac)
|
74
|
+
{
|
75
|
+
Check_Type(rb_mac, T_STRING);
|
76
|
+
|
77
|
+
if (cornflake_set_mac(StringValueCStr(rb_mac)) < 0)
|
78
|
+
rb_raise(rb_eTypeError, "Invalid MAC Address: %s", StringValueCStr(rb_mac));
|
79
|
+
|
80
|
+
return Qnil;
|
81
|
+
}
|
82
|
+
|
83
|
+
void Init_cornflake()
|
84
|
+
{
|
85
|
+
rb_cCornflake = rb_define_class("Cornflake", rb_cObject);
|
86
|
+
rb_define_singleton_method(rb_cCornflake, "new", rb_cornflake__new, 1);
|
87
|
+
rb_define_singleton_method(rb_cCornflake, "macaddr=", rb_cornflake_set_mac, 1);
|
88
|
+
rb_define_method(rb_cCornflake, "uid", rb_cornflake_gen, 0);
|
89
|
+
rb_define_method(rb_cCornflake, "hex", rb_cornflake_gen_hex, 0);
|
90
|
+
}
|
91
|
+
|
data/lib/cornflake.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'cornflake.so'
|
2
|
+
require 'macaddr'
|
3
|
+
|
4
|
+
Cornflake.macaddr = Mac.addr
|
5
|
+
|
6
|
+
class Cornflake
|
7
|
+
module Base62
|
8
|
+
SIXTYTWO = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a
|
9
|
+
PADDING = 18 # 18 digits is enough to last the next 300 years
|
10
|
+
|
11
|
+
def self.encode(numeric)
|
12
|
+
s = ''
|
13
|
+
while numeric > 0
|
14
|
+
s << Base62::SIXTYTWO[numeric.modulo(62)]
|
15
|
+
numeric /= 62
|
16
|
+
end
|
17
|
+
s.reverse!
|
18
|
+
s.rjust(PADDING, "0")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def base62
|
23
|
+
Base62.encode(self.hex.to_i(16))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
data/nginx/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Cornflake NGINX module
|
2
|
+
|
3
|
+
This is a self-contained nginx module that lets you use Cornflake to generate k-ordered IDs for all requests in a system. You can use these IDs to match requests between the frontend and the backend (or whatevs, buddy) and since they are roughly sorted, you can actually tell the order (and time) of the requests.
|
4
|
+
|
5
|
+
**WARNING**: This module is experimental as *fuck*. Do not deploy on production yet. It needs a lot more testing.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
1. Add `--add-module=../cornflake/nginx` to your nginx configure command. `make` dat sexy daemon. That will build the cornflake module.
|
10
|
+
|
11
|
+
2. Now you have a `$request_id` variable. Do things with it. E.g.
|
12
|
+
|
13
|
+
```
|
14
|
+
server {
|
15
|
+
listen 80;
|
16
|
+
server_name github.com;
|
17
|
+
location / {
|
18
|
+
proxy_set_header x-github-cornflake-id $request_id;
|
19
|
+
proxy_pass http://localhost:8080;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
```
|
23
|
+
|
24
|
+
Pick it up on the backend. Or whatever you want. Unclear.
|
25
|
+
|
26
|
+
## Credits
|
27
|
+
|
28
|
+
lol u know who wrote tis thang
|
data/nginx/cornflake.c
ADDED
@@ -0,0 +1,387 @@
|
|
1
|
+
#include <sys/types.h>
|
2
|
+
#include <sys/stat.h>
|
3
|
+
#include <sys/mman.h>
|
4
|
+
#include <fcntl.h>
|
5
|
+
#include <pthread.h>
|
6
|
+
#include <stdint.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <sys/time.h>
|
9
|
+
#include <string.h>
|
10
|
+
#include <unistd.h>
|
11
|
+
#include <errno.h>
|
12
|
+
#include <assert.h>
|
13
|
+
|
14
|
+
#include "cornflake.h"
|
15
|
+
|
16
|
+
#define cpu_relax() __asm__ volatile("pause\n": : :"memory")
|
17
|
+
|
18
|
+
typedef uint32_t spinlock_t;
|
19
|
+
|
20
|
+
static inline int spin_trylock(spinlock_t *lock)
|
21
|
+
{
|
22
|
+
size_t tries = 1000;
|
23
|
+
|
24
|
+
while (tries--) {
|
25
|
+
if (!__sync_lock_test_and_set(lock, 1))
|
26
|
+
return 0;
|
27
|
+
|
28
|
+
while (*lock) {
|
29
|
+
cpu_relax();
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
return -1;
|
34
|
+
}
|
35
|
+
|
36
|
+
static inline int spin_tryunlock(spinlock_t *lock)
|
37
|
+
{
|
38
|
+
if (__sync_lock_test_and_set(lock, 0) == 0)
|
39
|
+
return -1;
|
40
|
+
|
41
|
+
return 0;
|
42
|
+
}
|
43
|
+
|
44
|
+
static inline void spin_unlock(spinlock_t *lock)
|
45
|
+
{
|
46
|
+
__sync_lock_release(lock);
|
47
|
+
}
|
48
|
+
|
49
|
+
struct cornflake {
|
50
|
+
spinlock_t lock;
|
51
|
+
uint64_t last_time;
|
52
|
+
uint16_t counter;
|
53
|
+
};
|
54
|
+
|
55
|
+
static uint8_t g_local_mac[6];
|
56
|
+
|
57
|
+
static struct cornflake *
|
58
|
+
cornflake_open(const char *path)
|
59
|
+
{
|
60
|
+
int fd;
|
61
|
+
struct cornflake *corn;
|
62
|
+
|
63
|
+
fd = open(path, O_RDWR, 0666);
|
64
|
+
if (fd < 0)
|
65
|
+
return NULL;
|
66
|
+
|
67
|
+
corn = mmap(NULL, sizeof(*corn), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
68
|
+
close(fd);
|
69
|
+
|
70
|
+
return corn;
|
71
|
+
}
|
72
|
+
|
73
|
+
static struct cornflake *
|
74
|
+
cornflake_create(const char *path)
|
75
|
+
{
|
76
|
+
int fd;
|
77
|
+
struct cornflake *corn;
|
78
|
+
|
79
|
+
fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0666);
|
80
|
+
if (fd < 0) {
|
81
|
+
if (errno == EEXIST)
|
82
|
+
return cornflake_open(path);
|
83
|
+
|
84
|
+
return NULL;
|
85
|
+
}
|
86
|
+
|
87
|
+
if (ftruncate(fd, sizeof(*corn)) < 0) {
|
88
|
+
close(fd);
|
89
|
+
return NULL;
|
90
|
+
}
|
91
|
+
|
92
|
+
corn = mmap(NULL, sizeof(*corn), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
93
|
+
close(fd);
|
94
|
+
|
95
|
+
corn->lock = 0;
|
96
|
+
corn->last_time = 0;
|
97
|
+
corn->counter = 0;
|
98
|
+
|
99
|
+
return corn;
|
100
|
+
}
|
101
|
+
|
102
|
+
struct cornflake *
|
103
|
+
cornflake_new(const char *path)
|
104
|
+
{
|
105
|
+
struct cornflake *corn;
|
106
|
+
|
107
|
+
corn = cornflake_open(path);
|
108
|
+
if (corn)
|
109
|
+
return corn;
|
110
|
+
|
111
|
+
return cornflake_create(path);
|
112
|
+
}
|
113
|
+
|
114
|
+
void
|
115
|
+
cornflake_close(struct cornflake *corn)
|
116
|
+
{
|
117
|
+
munmap(corn, sizeof(*corn));
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Generate a 16-byte UID. The composition of the UID is as follows.
|
122
|
+
*
|
123
|
+
* [ 8 bytes .......][ 6 bytes ...][2 bytes]
|
124
|
+
* timestamp macaddr counter
|
125
|
+
*
|
126
|
+
* Where:
|
127
|
+
*
|
128
|
+
* - `timestamp` is the current time (in ms since the epoch) when the
|
129
|
+
* UID was generated
|
130
|
+
*
|
131
|
+
* - `macaddr` is the MAC address for this computer, in raw form
|
132
|
+
*
|
133
|
+
* - `counter` is a 16-bit counter to avoid collisions between UIDs
|
134
|
+
* generated in the same milisecond. This limits Cornflake to only
|
135
|
+
* 2^16 UID/s per milisecond.
|
136
|
+
*
|
137
|
+
* If successful, the `uid` buffer will be filled with the unique ID
|
138
|
+
* in raw form. Ensure that the buffer has 16 bytes before calling this
|
139
|
+
* function.
|
140
|
+
*
|
141
|
+
* Return values:
|
142
|
+
*
|
143
|
+
* - CORNFLAKE_OK (0): the UID was generated successfully
|
144
|
+
*
|
145
|
+
* - CORNFLAKE_ELOCK: there was an error when trying to lock the global
|
146
|
+
* state for UID generation. This is most likely because another process
|
147
|
+
* crashed while keeping the lock. As part of the error handling mechanism,
|
148
|
+
* we will free the global lock, so further calls to `cornflake_gen` should
|
149
|
+
* be able to work.
|
150
|
+
*
|
151
|
+
* - CORNFLAKE_EDRIFT: the system clock has drifted into the past. Cornflake
|
152
|
+
* will refuse to generate new UIDs until the clock reaches the time when the
|
153
|
+
* last UID was generated, as to ensure that no UIDs are tagged in the past.
|
154
|
+
*
|
155
|
+
* - CORNFLAKE_EOVERFLOW: the counter in the UID has overflowed (there have been
|
156
|
+
* more than 2^16 unique IDs generated in this milisecond). You need to wait at
|
157
|
+
* least 1 milisecond and try again.
|
158
|
+
*/
|
159
|
+
int
|
160
|
+
cornflake_gen(uint8_t *uid, struct cornflake *corn)
|
161
|
+
{
|
162
|
+
struct timeval tv;
|
163
|
+
uint64_t timestamp;
|
164
|
+
uint16_t sequence = 0;
|
165
|
+
uint8_t *bytes;
|
166
|
+
|
167
|
+
/*
|
168
|
+
* We need to synchronize the global state so only *one* Cornflake
|
169
|
+
* process generates UIDs simultaneously.
|
170
|
+
*
|
171
|
+
* We use a spinlock in shared memory (this requires x86/x64 semantics,
|
172
|
+
* do not try this at home, children, specially if you have an ARM
|
173
|
+
* processor at home).
|
174
|
+
*
|
175
|
+
* There is a caveat when locking this global state: there's an infintesimal
|
176
|
+
* chance that another Cornflake process got terminated (i.e. segmentation
|
177
|
+
* fault, uncaught signal) during the ~20 instructions that happen inside
|
178
|
+
* this lock, before the lock has been released.
|
179
|
+
*
|
180
|
+
* This would usually cause every other process in the system to deadlock
|
181
|
+
* while waiting for the spinlock. To work around this, we use a custom
|
182
|
+
* `trylock` the spinlock: we're going to try a thousand (1000) long locks
|
183
|
+
* in a row (i.e. try the exchange, if failed, loop until the lock is free,
|
184
|
+
* try the exchange again).
|
185
|
+
*
|
186
|
+
* If we haven't acquired a lock after 1000 tries, we'll assume the spinlock
|
187
|
+
* has deadlocked and we'll simply drop it and fail fast so other processes
|
188
|
+
* can continue UID generations. This all should happen in less than ~500ms
|
189
|
+
* even in the highest contention cases.
|
190
|
+
*
|
191
|
+
* FAQ: Why are you using an `xchg` spinlock instead of e.g. `pthread_mutex` on
|
192
|
+
* shared memory or `pthread_spinlock`? All the `pthread` synchronization
|
193
|
+
* primitives have undefined behavior when trying to unlock/destroy them from
|
194
|
+
* a different thread than the one who originally acquired the lock. By controlling
|
195
|
+
* the spinlock semantics, we can ensure the lock gets released when a process dies
|
196
|
+
* holding it *and* we can verify that other processes do not race on the released
|
197
|
+
* lock.
|
198
|
+
*/
|
199
|
+
if (spin_trylock(&corn->lock) < 0) {
|
200
|
+
spin_unlock(&corn->lock);
|
201
|
+
return CORNFLAKE_ELOCK;
|
202
|
+
}
|
203
|
+
|
204
|
+
/*
|
205
|
+
* Get the current time in ms -- this will be the timestamp that shows
|
206
|
+
* on the first 8 bytes of the UID
|
207
|
+
*/
|
208
|
+
gettimeofday(&tv, NULL);
|
209
|
+
timestamp = ((uint64_t)tv.tv_sec * 1000) +
|
210
|
+
((uint64_t)tv.tv_usec / 1000);
|
211
|
+
|
212
|
+
/*
|
213
|
+
* If the current timestamp is behind the timestamp of the last UID we've
|
214
|
+
* generated, that means our system clock has drifted. We cannot generate
|
215
|
+
* new UIDs until the clock is back on time.
|
216
|
+
*/
|
217
|
+
if (timestamp < corn->last_time) {
|
218
|
+
spin_unlock(&corn->lock);
|
219
|
+
return CORNFLAKE_EDRIFT;
|
220
|
+
}
|
221
|
+
|
222
|
+
/*
|
223
|
+
* If the current timestamp is the same as the last UID's we've generated,
|
224
|
+
* we need to increase the UID counter (last 16 bits of the UID) to prevent
|
225
|
+
* collisions.
|
226
|
+
*
|
227
|
+
* When the UID counter wraps around (i.e. reaches 0), we've generated more
|
228
|
+
* than 2^16 UIDs in the millisecond, so we need to wait to the next ms
|
229
|
+
* in order to prevent collisions.
|
230
|
+
*/
|
231
|
+
if (timestamp == corn->last_time) {
|
232
|
+
sequence = ++corn->counter;
|
233
|
+
if (!sequence) {
|
234
|
+
spin_unlock(&corn->lock);
|
235
|
+
return CORNFLAKE_EOVERFLOW;
|
236
|
+
}
|
237
|
+
} else {
|
238
|
+
corn->last_time = timestamp;
|
239
|
+
corn->counter = 0;
|
240
|
+
}
|
241
|
+
|
242
|
+
/*
|
243
|
+
* We're out of the locking zone. HOWEVER: we need to ensure that the lock
|
244
|
+
* we've acquired is still acquired when we unlock it. If somehow the lock
|
245
|
+
* is not taken when we try to unlock it, that means another process has
|
246
|
+
* decided that we're "dead" (we took too long to generate this UID!) and
|
247
|
+
* unlocked the lock for us.
|
248
|
+
*
|
249
|
+
* In that case, we cannot continue: the UID we've just generated could very
|
250
|
+
* well be racy.
|
251
|
+
*/
|
252
|
+
if (spin_tryunlock(&corn->lock) < 0)
|
253
|
+
return CORNFLAKE_ELOCK;
|
254
|
+
|
255
|
+
/*
|
256
|
+
* Move over all the generated data to network byte order (big endian).
|
257
|
+
* This is to ensure that the UIDs are k-ordered when compared
|
258
|
+
* lexicographically.
|
259
|
+
*/
|
260
|
+
bytes = (uint8_t *)×tamp;
|
261
|
+
uid[0] = bytes[7];
|
262
|
+
uid[1] = bytes[6];
|
263
|
+
uid[2] = bytes[5];
|
264
|
+
uid[3] = bytes[4];
|
265
|
+
uid[4] = bytes[3];
|
266
|
+
uid[5] = bytes[2];
|
267
|
+
uid[6] = bytes[1];
|
268
|
+
uid[7] = bytes[0];
|
269
|
+
|
270
|
+
memcpy(&uid[8], g_local_mac, sizeof(g_local_mac));
|
271
|
+
|
272
|
+
bytes = (uint8_t *)&sequence;
|
273
|
+
uid[14] = bytes[1];
|
274
|
+
uid[15] = bytes[0];
|
275
|
+
|
276
|
+
return 0;
|
277
|
+
}
|
278
|
+
|
279
|
+
int
|
280
|
+
cornflake_gen_hex(char *hex_uid, size_t len, struct cornflake *corn)
|
281
|
+
{
|
282
|
+
static char to_hex[] = "0123456789abcdef";
|
283
|
+
|
284
|
+
uint8_t uid[16];
|
285
|
+
int i, ret;
|
286
|
+
|
287
|
+
if (len < 33)
|
288
|
+
return CORNFLAKE_EOVERFLOW;
|
289
|
+
|
290
|
+
ret = cornflake_gen(uid, corn);
|
291
|
+
if (ret < 0)
|
292
|
+
return ret;
|
293
|
+
|
294
|
+
for (i = 0; i < 16; ++i) {
|
295
|
+
*hex_uid++ = to_hex[uid[i] >> 4];
|
296
|
+
*hex_uid++ = to_hex[uid[i] & 0xf];
|
297
|
+
}
|
298
|
+
|
299
|
+
*hex_uid = '\0';
|
300
|
+
return 0;
|
301
|
+
}
|
302
|
+
|
303
|
+
int
|
304
|
+
cornflake_set_mac(const char *mac_addr)
|
305
|
+
{
|
306
|
+
if (strlen(mac_addr) != 17)
|
307
|
+
return CORNFLAKE_EMACADDR;
|
308
|
+
|
309
|
+
if (sscanf(mac_addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
310
|
+
&g_local_mac[0], &g_local_mac[1], &g_local_mac[2],
|
311
|
+
&g_local_mac[3], &g_local_mac[4], &g_local_mac[5]) != 6)
|
312
|
+
return CORNFLAKE_EMACADDR;
|
313
|
+
|
314
|
+
return 0;
|
315
|
+
}
|
316
|
+
|
317
|
+
#ifdef CORNFLAKE_STANDALONE
|
318
|
+
#include "getopt.h"
|
319
|
+
|
320
|
+
static inline double bench_time(void)
|
321
|
+
{
|
322
|
+
struct timespec ts;
|
323
|
+
clock_gettime(CLOCK_REALTIME, &ts);
|
324
|
+
return (ts.tv_sec * 1000.0) + (ts.tv_nsec / 1000000.0);
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
int main(int argc, char *argv[])
|
329
|
+
{
|
330
|
+
static struct option longopts[] = {
|
331
|
+
{ "count", required_argument, NULL, 'n' },
|
332
|
+
{ "path", required_argument, NULL, 'o' },
|
333
|
+
{ "time", no_argument, NULL, 't' },
|
334
|
+
{ "version", no_argument, NULL, 'v' },
|
335
|
+
{ NULL, 0, NULL, 0 }
|
336
|
+
};
|
337
|
+
|
338
|
+
struct cornflake *flake;
|
339
|
+
const char *flake_path = "./milkbowl";
|
340
|
+
int opt, benchmark = 0;
|
341
|
+
unsigned int i, count = 1;
|
342
|
+
double start, end;
|
343
|
+
|
344
|
+
while ((opt = getopt_long(argc, argv, ":n:o:tv", longopts, NULL)) != -1) {
|
345
|
+
switch (opt) {
|
346
|
+
case 'n': count = atoi(optarg); break;
|
347
|
+
case 'o': flake_path = optarg; break;
|
348
|
+
case 't': benchmark = 1; break;
|
349
|
+
case 'v':
|
350
|
+
puts("cornflake");
|
351
|
+
return 0;
|
352
|
+
|
353
|
+
default:
|
354
|
+
printf("Usage: %s [--path CORNFLAKE_CONTEXT] [-n COUNT]\n", argv[0]);
|
355
|
+
return 1;
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
flake = cornflake_new(flake_path);
|
360
|
+
if (flake == NULL) {
|
361
|
+
fprintf(stderr, "Failed to open Cornflake context: %s\n", flake_path);
|
362
|
+
return -1;
|
363
|
+
}
|
364
|
+
|
365
|
+
start = bench_time();
|
366
|
+
|
367
|
+
for (i = 0; i < count; ++i) {
|
368
|
+
char uid[33];
|
369
|
+
int err = cornflake_gen_hex(uid, sizeof(uid), flake);
|
370
|
+
|
371
|
+
if (err < 0) {
|
372
|
+
fprintf(stderr, "Generation failed: %d\n", err);
|
373
|
+
return -2;
|
374
|
+
}
|
375
|
+
|
376
|
+
puts(uid);
|
377
|
+
}
|
378
|
+
|
379
|
+
end = bench_time();
|
380
|
+
|
381
|
+
if (benchmark)
|
382
|
+
fprintf(stderr, "Benchmark: %d oids in %fms\n", count, end - start);
|
383
|
+
|
384
|
+
cornflake_close(flake);
|
385
|
+
return 0;
|
386
|
+
}
|
387
|
+
#endif
|
data/nginx/cornflake.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef __CORNFLAKE_H__
|
2
|
+
#define __CORNFLAKE_H__
|
3
|
+
|
4
|
+
struct cornflake;
|
5
|
+
|
6
|
+
enum cornflake_err {
|
7
|
+
CORNFLAKE_OK = 0,
|
8
|
+
CORNFLAKE_ELOCK = -1,
|
9
|
+
CORNFLAKE_EDRIFT = -2,
|
10
|
+
CORNFLAKE_EMACADDR = -3,
|
11
|
+
CORNFLAKE_EOVERFLOW = -4,
|
12
|
+
};
|
13
|
+
|
14
|
+
struct cornflake *cornflake_new(const char *path);
|
15
|
+
void cornflake_close(struct cornflake *corn);
|
16
|
+
int cornflake_gen(uint8_t *uid_out, struct cornflake *corn);
|
17
|
+
int cornflake_gen_hex(char *hex_uid, size_t len, struct cornflake *corn);
|
18
|
+
int cornflake_set_mac(const char *mac_addr);
|
19
|
+
|
20
|
+
#endif
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#include <ngx_core.h>
|
2
|
+
#include <ngx_http.h>
|
3
|
+
#include <nginx.h>
|
4
|
+
#include <ngx_http_variables.h>
|
5
|
+
#include "cornflake.h"
|
6
|
+
|
7
|
+
static struct cornflake *g_cornflake;
|
8
|
+
|
9
|
+
ngx_int_t
|
10
|
+
ngx_cornflake_get_var(ngx_http_request_t *r,
|
11
|
+
ngx_http_variable_value_t *v, uintptr_t data)
|
12
|
+
{
|
13
|
+
char *uid = ngx_pnalloc(r->pool, 33);
|
14
|
+
|
15
|
+
if (uid == NULL)
|
16
|
+
return NGX_ERROR;
|
17
|
+
|
18
|
+
if (cornflake_gen_hex(uid, 33, g_cornflake) < 0) {
|
19
|
+
ngx_pfree(r->pool, uid);
|
20
|
+
return NGX_ERROR;
|
21
|
+
}
|
22
|
+
|
23
|
+
v->len = 32;
|
24
|
+
v->valid = 1;
|
25
|
+
v->no_cacheable = 0;
|
26
|
+
v->not_found = 0;
|
27
|
+
v->data = (u_char *)uid;
|
28
|
+
|
29
|
+
return NGX_OK;
|
30
|
+
}
|
31
|
+
|
32
|
+
static ngx_int_t add_variables(ngx_conf_t *cf)
|
33
|
+
{
|
34
|
+
static ngx_str_t var_name = ngx_string("request_id");
|
35
|
+
ngx_http_variable_t *var = NULL;
|
36
|
+
|
37
|
+
g_cornflake = cornflake_new("/etc/cornflake");
|
38
|
+
if (g_cornflake == NULL)
|
39
|
+
return NGX_ERROR;
|
40
|
+
|
41
|
+
var = ngx_http_add_variable(cf, &var_name, NGX_HTTP_VAR_NOHASH);
|
42
|
+
if (var == NULL)
|
43
|
+
return NGX_ERROR;
|
44
|
+
|
45
|
+
var->get_handler = ngx_cornflake_get_var;
|
46
|
+
return NGX_OK;
|
47
|
+
}
|
48
|
+
|
49
|
+
static ngx_http_module_t cornflake_context = {
|
50
|
+
add_variables, /* preconfiguration */
|
51
|
+
NULL, /* postconfiguration */
|
52
|
+
|
53
|
+
NULL, /* create main configuration */
|
54
|
+
NULL, /* init main configuration */
|
55
|
+
|
56
|
+
NULL, /* create server configuration */
|
57
|
+
NULL, /* merge server configuration */
|
58
|
+
|
59
|
+
NULL, /* create location configuration */
|
60
|
+
NULL /* merge location configuration */
|
61
|
+
};
|
62
|
+
|
63
|
+
static ngx_command_t cornflake_commands[] = {
|
64
|
+
ngx_null_command
|
65
|
+
};
|
66
|
+
|
67
|
+
ngx_module_t ngx_cornflake_module = {
|
68
|
+
NGX_MODULE_V1,
|
69
|
+
&cornflake_context, /* module context */
|
70
|
+
cornflake_commands, /* module directives */
|
71
|
+
NGX_HTTP_MODULE, /* module type */
|
72
|
+
NULL, /* init master */
|
73
|
+
NULL, /* init module */
|
74
|
+
NULL, /* init process */
|
75
|
+
NULL, /* init thread */
|
76
|
+
NULL, /* exit thread */
|
77
|
+
NULL, /* exit process */
|
78
|
+
NULL, /* exit master */
|
79
|
+
NGX_MODULE_V1_PADDING
|
80
|
+
};
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cornflake
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vicent Martí
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: macaddr
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
description: This does the thing with the ID generation and all that
|
28
|
+
email: vmg@github.com
|
29
|
+
executables: []
|
30
|
+
extensions:
|
31
|
+
- ext/cornflake/extconf.rb
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- Gemfile.lock
|
36
|
+
- Rakefile
|
37
|
+
- bin/Makefile
|
38
|
+
- bin/race.sh
|
39
|
+
- cornflake.gemspec
|
40
|
+
- ext/cornflake/cornflake.c
|
41
|
+
- ext/cornflake/cornflake.h
|
42
|
+
- ext/cornflake/cornflake.rb.c
|
43
|
+
- ext/cornflake/extconf.rb
|
44
|
+
- lib/cornflake.rb
|
45
|
+
- nginx/README.md
|
46
|
+
- nginx/cornflake.c
|
47
|
+
- nginx/cornflake.h
|
48
|
+
- nginx/ngx_cornflake_module.c
|
49
|
+
homepage: http://github.com/github/cornflake
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.0.7
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: K-ordered id generation, brought to you by Unix (TM)
|
73
|
+
test_files: []
|