cornflake 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org/"
2
+ gemspec
3
+
4
+ gem "rake-compiler"
5
+ gem "minitest"
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
@@ -0,0 +1,8 @@
1
+ require 'rake/clean'
2
+ require 'rake/extensiontask'
3
+
4
+ # Ruby Extension
5
+ Rake::ExtensionTask.new('cornflake')
6
+
7
+ # Packaging
8
+ require 'bundler/gem_tasks'
data/bin/Makefile ADDED
@@ -0,0 +1,6 @@
1
+ cornflake: ../ext/cornflake/cornflake.c
2
+ gcc -O2 -Wno-unused-result \
3
+ -DCORNFLAKE_STANDALONE \
4
+ ../ext/cornflake/cornflake.c \
5
+ -lpthread \
6
+ -o cornflake
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 *)&timestamp;
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
+
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('cornflake')
4
+ create_makefile('cornflake')
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 *)&timestamp;
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: []