iodine 0.4.19 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -8,7 +8,7 @@ Feel free to copy, use and enjoy according to the license provided.
8
8
  /**
9
9
 
10
10
  This is a customized version for command line interface (CLI) arguments. All
11
- arguments are converted all command line arguments into a key-value paired Hash.
11
+ arguments are converted into a key-value paired Hash.
12
12
 
13
13
  The CLI helper automatically provides `-?`, `-h` and `-help` support that
14
14
  prints a short explanation for every option and exits.
@@ -39,8 +39,10 @@ EXAMPLE:
39
39
  const char *port = fio_cli_get_str("port");
40
40
  if (!port)
41
41
  port = "3000";
42
+ // use parsed information.
43
+ // ...
44
+ // cleanup
42
45
  fio_cli_end();
43
- // .. use parsed information.
44
46
 
45
47
 
46
48
  */
@@ -51,10 +53,13 @@ EXAMPLE:
51
53
  extern "C" {
52
54
  #endif
53
55
 
54
- /** Initialize the CLI helper and adds the `info` string to the help section */
56
+ /** Initialize the CLI helper and adds the `info` string to the help section. */
55
57
  void fio_cli_start(int argc, const char **argv, const char *info);
56
58
 
57
- /** Clears the memory and resources used by the CLI helper */
59
+ /** Tells the CLI helper to ignore unrecognized command line arguments. */
60
+ void fio_cli_ignore_unknown(void);
61
+
62
+ /** Clears the memory and resources related to the CLI helper. */
58
63
  void fio_cli_end(void);
59
64
 
60
65
  /**
@@ -0,0 +1,759 @@
1
+ #ifndef H_FIO_SIMPLE_HASH_H
2
+ /*
3
+ Copyright: Boaz Segev, 2017-2018
4
+ License: MIT
5
+ */
6
+
7
+ /**
8
+ * A simple ordered Hash Table implementation, with a minimal API and zero hash
9
+ * collision protection.
10
+ *
11
+ * Unique keys are required. Full key collisions aren't handled, instead the old
12
+ * value is replaced and returned.
13
+ *
14
+ * Partial key collisions are handled by seeking forward and attempting to find
15
+ * a close enough spot. If a close enough spot isn't found, rehashing is
16
+ * initiated and memory consumption increases.
17
+ *
18
+ * The Hash Table is ordered using an internal ordered array of data containers
19
+ * with duplicates of the key data (to improve cache locality).
20
+ *
21
+ * The file was written to be compatible with C++ as well as C, hence some
22
+ * pointer casting.
23
+ */
24
+ #define H_FIO_SIMPLE_HASH_H
25
+
26
+ #ifndef _GNU_SOURCE
27
+ #define _GNU_SOURCE
28
+ #endif
29
+
30
+ #ifndef FIO_FUNC
31
+ #define FIO_FUNC static __attribute__((unused))
32
+ #endif
33
+
34
+ #include <errno.h>
35
+ #include <stdint.h>
36
+ #include <stdio.h>
37
+ #include <stdlib.h>
38
+ #include <string.h>
39
+
40
+ /**
41
+ * extra collision protection can be obtained by defining ALL of the following:
42
+ * * FIO_HASH_KEY_TYPE - the type used for keys.
43
+ * * FIO_HASH_KEY_INVALID - an invalid key with it's bytes set to zero.
44
+ * * FIO_HASH_KEY2UINT(key) - converts a key to a hash value number.
45
+ * * FIO_HASH_COMPARE_KEYS(k1, k2) - compares two key.
46
+ * * FIO_HASH_KEY_ISINVALID(key) - tests for an invalid key.
47
+ * * FIO_HASH_KEY_COPY(key) - creates a persistent copy of the key.
48
+ * * FIO_HASH_KEY_DESTROY(key) - destroys (or frees) the key's copy.
49
+ *
50
+ * Note: FIO_HASH_COMPARE_KEYS will be used to compare against
51
+ * FIO_HASH_KEY_INVALID as well as valid keys.
52
+ *
53
+ * Note: Before freeing the Hash, FIO_HASH_KEY_DESTROY should be called for
54
+ * every key. This is NOT automatic. see the FIO_HASH_FOR_EMPTY(h) macro.
55
+ */
56
+ #if !defined(FIO_HASH_COMPARE_KEYS) || !defined(FIO_HASH_KEY_TYPE) || \
57
+ !defined(FIO_HASH_KEY2UINT) || !defined(FIO_HASH_KEY_INVALID) || \
58
+ !defined(FIO_HASH_KEY_ISINVALID) || !defined(FIO_HASH_KEY_COPY) || \
59
+ !defined(FIO_HASH_KEY_DESTROY)
60
+ #define FIO_HASH_KEY_TYPE uint64_t
61
+ #define FIO_HASH_KEY_INVALID 0
62
+ #define FIO_HASH_KEY2UINT(key) (key)
63
+ #define FIO_HASH_COMPARE_KEYS(k1, k2) ((k1) == (k2))
64
+ #define FIO_HASH_KEY_ISINVALID(key) ((key) == 0)
65
+ #define FIO_HASH_KEY_COPY(key) (key)
66
+ #define FIO_HASH_KEY_DESTROY(key) ((void)0)
67
+ #elif !defined(FIO_HASH_NO_TEST)
68
+ #define FIO_HASH_NO_TEST 1
69
+ #endif
70
+
71
+ #ifndef FIO_HASH_INITIAL_CAPACITY
72
+ /* MUST be a power of 2 */
73
+ #define FIO_HASH_INITIAL_CAPACITY 4
74
+ #endif
75
+
76
+ #ifndef FIO_HASH_MAX_MAP_SEEK
77
+ /* MUST be a power of 2 */
78
+ #define FIO_HASH_MAX_MAP_SEEK (256)
79
+ #endif
80
+
81
+ #ifndef FIO_HASH_REALLOC /* NULL ptr indicates new allocation */
82
+ #define FIO_HASH_REALLOC(ptr, original_size, new_size, valid_data_length) \
83
+ realloc((ptr), (new_size))
84
+ #endif
85
+ #ifndef FIO_HASH_CALLOC
86
+ #define FIO_HASH_CALLOC(size, count) calloc((size), (count))
87
+ #endif
88
+ #ifndef FIO_HASH_FREE
89
+ #define FIO_HASH_FREE(ptr, size) free((ptr))
90
+ #endif
91
+
92
+ /* *****************************************************************************
93
+ Hash API
94
+ ***************************************************************************** */
95
+
96
+ /** The Hash Table container type. */
97
+ typedef struct fio_hash_s fio_hash_s;
98
+
99
+ /** Allocates and initializes internal data and resources. */
100
+ FIO_FUNC void fio_hash_new(fio_hash_s *hash);
101
+
102
+ /** Allocates and initializes internal data and resources with the requested
103
+ * capacity. */
104
+ FIO_FUNC void fio_hash_new2(fio_hash_s *hash, size_t capa);
105
+
106
+ /** Deallocates any internal resources. */
107
+ FIO_FUNC void fio_hash_free(fio_hash_s *hash);
108
+
109
+ /** Locates an object in the Hash Map Table according to the hash key value. */
110
+ FIO_FUNC inline void *fio_hash_find(fio_hash_s *hash, FIO_HASH_KEY_TYPE key);
111
+
112
+ /**
113
+ * Inserts an object to the Hash Map Table, rehashing if required, returning the
114
+ * old object if it exists.
115
+ *
116
+ * Set obj to NULL to remove an existing data (the existing object will be
117
+ * returned).
118
+ */
119
+ FIO_FUNC void *fio_hash_insert(fio_hash_s *hash, FIO_HASH_KEY_TYPE key,
120
+ void *obj);
121
+
122
+ /**
123
+ * Allows the Hash to be momenterally used as a stack, poping the last element
124
+ * entered.
125
+ *
126
+ * If a pointer to `key` is provided, the element's key will be placed in it's
127
+ * place.
128
+ *
129
+ * Remember that keys are likely to be freed as well (`FIO_HASH_KEY_DESTROY`).
130
+ */
131
+ FIO_FUNC void *fio_hash_pop(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key);
132
+
133
+ /**
134
+ * Allows a peak at the Hash's last element.
135
+ *
136
+ * If a pointer to `key` is provided, the element's key will be placed in it's
137
+ * place.
138
+ *
139
+ * Remember that keys might be destroyed if the Hash is altered
140
+ * (`FIO_HASH_KEY_DESTROY`).
141
+ */
142
+ FIO_FUNC void *fio_hash_last(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key);
143
+
144
+ /** Returns the number of elements currently in the Hash Table. */
145
+ FIO_FUNC inline size_t fio_hash_count(const fio_hash_s *hash);
146
+
147
+ /**
148
+ * Returns a temporary theoretical Hash map capacity.
149
+ * This could be used for testing performance and memory consumption.
150
+ */
151
+ FIO_FUNC inline size_t fio_hash_capa(const fio_hash_s *hash);
152
+
153
+ /**
154
+ * Attempts to minimize memory usage by removing empty spaces caused by deleted
155
+ * items and rehashing the Hash Map.
156
+ *
157
+ * Returns the updated hash map capacity.
158
+ */
159
+ FIO_FUNC inline size_t fio_hash_compact(fio_hash_s *hash);
160
+
161
+ /** Forces a rehashing of the hash. */
162
+ FIO_FUNC void fio_hash_rehash(fio_hash_s *hash);
163
+
164
+ /**
165
+ * Iteration using a callback for each entry in the Hash Table.
166
+ *
167
+ * The callback task function must accept the hash key, the entry data and an
168
+ * opaque user pointer:
169
+ *
170
+ * int example_task(FIO_HASH_KEY_TYPE key, void *obj, void *arg) {return 0;}
171
+ *
172
+ * If the callback returns -1, the loop is broken. Any other value is ignored.
173
+ *
174
+ * Returns the relative "stop" position, i.e., the number of items processed +
175
+ * the starting point.
176
+ */
177
+ FIO_FUNC inline size_t fio_hash_each(fio_hash_s *hash, const size_t start_at,
178
+ int (*task)(FIO_HASH_KEY_TYPE key,
179
+ void *obj, void *arg),
180
+ void *arg);
181
+
182
+ /**
183
+ * A macro for a `for` loop that iterates over all the hashed objects (in
184
+ * order).
185
+ *
186
+ * `hash` a pointer to the hash table variable and `i` is a temporary variable
187
+ * name to be created for iteration.
188
+ *
189
+ * `i->key` is the key and `i->obj` is the hashed data.
190
+ */
191
+ #define FIO_HASH_FOR_LOOP(hash, i)
192
+
193
+ /**
194
+ * A macro for a `for` loop that will iterate over all the hashed objects (in
195
+ * order) and empties the hash, later calling `fio_hash_free` to free the hash
196
+ * (but not the container).
197
+ *
198
+ * `hash` a pointer to the hash table variable and `i` is a temporary variable
199
+ * name to be created for iteration.
200
+ *
201
+ * `i->key` is the key and `i->obj` is the hashed data.
202
+ *
203
+ * Free the objects and the Hash Map container manually (if required). Custom
204
+ * keys will be freed automatically when using this macro.
205
+ *
206
+ */
207
+ #define FIO_HASH_FOR_FREE(hash, i)
208
+
209
+ /**
210
+ * A macro for a `for` loop that iterates over all the hashed objects (in
211
+ * order) and empties the hash.
212
+ *
213
+ * This will also reallocate the map's memory (to zero out the data), so if this
214
+ * is performed before calling `fio_hash_free`, use FIO_HASH_FOR_FREE instead.
215
+ *
216
+ * `hash` a pointer to the hash table variable and `i` is a temporary variable
217
+ * name to be created for iteration.
218
+ *
219
+ * `i->key` is the key and `i->obj` is the hashed data.
220
+ *
221
+ * Free the objects and the Hash Map container manually (if required). Custom
222
+ * keys will be freed automatically when using this macro.
223
+ *
224
+ */
225
+ #define FIO_HASH_FOR_EMPTY(hash, i)
226
+
227
+ /* *****************************************************************************
228
+ Hash Table Internal Data Structures
229
+ ***************************************************************************** */
230
+
231
+ typedef struct fio_hash_data_ordered_s {
232
+ FIO_HASH_KEY_TYPE key; /* another copy for memory cache locality */
233
+ void *obj;
234
+ } fio_hash_data_ordered_s;
235
+
236
+ typedef struct fio_hash_data_s {
237
+ FIO_HASH_KEY_TYPE key; /* another copy for memory cache locality */
238
+ struct fio_hash_data_ordered_s *obj;
239
+ } fio_hash_data_s;
240
+
241
+ /* the information in the Hash Map structure should be considered READ ONLY. */
242
+ struct fio_hash_s {
243
+ uintptr_t count;
244
+ uintptr_t capa;
245
+ uintptr_t pos;
246
+ uintptr_t mask;
247
+ fio_hash_data_ordered_s *ordered;
248
+ fio_hash_data_s *map;
249
+ };
250
+
251
+ #undef FIO_HASH_FOR_LOOP
252
+ #define FIO_HASH_FOR_LOOP(hash, container) \
253
+ for (fio_hash_data_ordered_s *container = (hash)->ordered; \
254
+ container && (container < (hash)->ordered + (hash)->pos); ++container)
255
+
256
+ #undef FIO_HASH_FOR_FREE
257
+ #define FIO_HASH_FOR_FREE(hash, container) \
258
+ for (fio_hash_data_ordered_s *container = (hash)->ordered; \
259
+ (container && container >= (hash)->ordered && \
260
+ (container < (hash)->ordered + (hash)->pos)) || \
261
+ ((fio_hash_free(hash), (hash)->ordered) != NULL); \
262
+ FIO_HASH_KEY_DESTROY(container->key), (++container))
263
+
264
+ #undef FIO_HASH_FOR_EMPTY
265
+ #define FIO_HASH_FOR_EMPTY(hash, container) \
266
+ for (fio_hash_data_ordered_s *container = (hash)->ordered; \
267
+ (container && (container < (hash)->ordered + (hash)->pos)) || \
268
+ (memset((hash)->map, 0, (hash)->capa * sizeof(*(hash)->map)), \
269
+ ((hash)->pos = (hash)->count = 0)); \
270
+ (FIO_HASH_KEY_DESTROY(container->key), \
271
+ container->key = FIO_HASH_KEY_INVALID, container->obj = NULL), \
272
+ (++container))
273
+ #define FIO_HASH_INIT \
274
+ { .capa = 0 }
275
+
276
+ /* *****************************************************************************
277
+ Hash allocation / deallocation.
278
+ ***************************************************************************** */
279
+
280
+ /** Allocates and initializes internal data and resources with the requested
281
+ * capacity. */
282
+ FIO_FUNC void fio_hash__new__internal__safe_capa(fio_hash_s *h, size_t capa) {
283
+ *h = (fio_hash_s){
284
+ .mask = (capa - 1),
285
+ .map = (fio_hash_data_s *)FIO_HASH_CALLOC(sizeof(*h->map), capa),
286
+ .ordered =
287
+ (fio_hash_data_ordered_s *)FIO_HASH_CALLOC(sizeof(*h->ordered), capa),
288
+ .capa = capa,
289
+ };
290
+ if (!h->map || !h->ordered) {
291
+ perror("ERROR: Hash Table couldn't allocate memory");
292
+ exit(errno);
293
+ }
294
+ h->ordered[0] =
295
+ (fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
296
+ }
297
+
298
+ /** Allocates and initializes internal data and resources with the requested
299
+ * capacity. */
300
+ FIO_FUNC void fio_hash_new2(fio_hash_s *h, size_t capa) {
301
+ size_t act_capa = 1;
302
+ while (act_capa < capa)
303
+ act_capa = act_capa << 1;
304
+ fio_hash__new__internal__safe_capa(h, act_capa);
305
+ }
306
+
307
+ FIO_FUNC void fio_hash_new(fio_hash_s *h) {
308
+ fio_hash__new__internal__safe_capa(h, FIO_HASH_INITIAL_CAPACITY);
309
+ }
310
+
311
+ FIO_FUNC void fio_hash_free(fio_hash_s *h) {
312
+ FIO_HASH_FREE(h->map, h->capa);
313
+ FIO_HASH_FREE(h->ordered, h->capa);
314
+ *h = (fio_hash_s){.map = NULL};
315
+ }
316
+
317
+ /* *****************************************************************************
318
+ Internal HashMap Functions
319
+ ***************************************************************************** */
320
+ FIO_FUNC inline uintptr_t fio_hash_map_cuckoo_steps(uintptr_t step) {
321
+ return (step * 3);
322
+ }
323
+
324
+ /* seeks the hash's position in the map */
325
+ FIO_FUNC fio_hash_data_s *fio_hash_seek_pos_(fio_hash_s *hash,
326
+ FIO_HASH_KEY_TYPE key) {
327
+ /* TODO: consider implementing Robing Hood reordering during seek? */
328
+ fio_hash_data_s *pos = hash->map + (FIO_HASH_KEY2UINT(key) & hash->mask);
329
+ uintptr_t i = 0;
330
+ const uintptr_t limit = hash->capa > FIO_HASH_MAX_MAP_SEEK
331
+ ? FIO_HASH_MAX_MAP_SEEK
332
+ : ((hash->capa >> 1) | 1);
333
+ while (i < limit) {
334
+ if (FIO_HASH_KEY_ISINVALID(pos->key) ||
335
+ (FIO_HASH_KEY2UINT(pos->key) == FIO_HASH_KEY2UINT(key) &&
336
+ FIO_HASH_COMPARE_KEYS(pos->key, key)))
337
+ return pos;
338
+ pos = hash->map + (((FIO_HASH_KEY2UINT(key) & hash->mask) +
339
+ fio_hash_map_cuckoo_steps(i++)) &
340
+ hash->mask);
341
+ }
342
+ return NULL;
343
+ }
344
+
345
+ /* finds an object in the map */
346
+ FIO_FUNC inline void *fio_hash_find(fio_hash_s *hash, FIO_HASH_KEY_TYPE key) {
347
+ if (!hash->map)
348
+ return NULL;
349
+ fio_hash_data_s *info = fio_hash_seek_pos_(hash, key);
350
+ if (!info || !info->obj)
351
+ return NULL;
352
+ return (void *)info->obj->obj;
353
+ }
354
+
355
+ /* inserts an object to the map, rehashing if required, returning old object.
356
+ * set obj to NULL to remove existing data.
357
+ */
358
+ FIO_FUNC void *fio_hash_insert(fio_hash_s *hash, FIO_HASH_KEY_TYPE key,
359
+ void *obj) {
360
+ /* ensure some space */
361
+ if (obj && hash->pos >= hash->capa)
362
+ fio_hash_rehash(hash);
363
+
364
+ /* find where the object belongs in the map */
365
+ fio_hash_data_s *info = fio_hash_seek_pos_(hash, key);
366
+ if (!info && !obj)
367
+ return NULL;
368
+ while (!info) {
369
+ fio_hash_rehash(hash);
370
+ info = fio_hash_seek_pos_(hash, key);
371
+ }
372
+
373
+ if (!info->obj) {
374
+ /* a fresh object */
375
+
376
+ if (obj == NULL) {
377
+ /* nothing to delete */
378
+ return NULL;
379
+ }
380
+
381
+ /* add object to ordered hash */
382
+ hash->ordered[hash->pos] =
383
+ (fio_hash_data_ordered_s){.key = FIO_HASH_KEY_COPY(key), .obj = obj};
384
+
385
+ /* add object to map */
386
+ *info = (fio_hash_data_s){.key = hash->ordered[hash->pos].key,
387
+ .obj = hash->ordered + hash->pos};
388
+
389
+ /* manage counters and mark end position */
390
+ hash->count++;
391
+ hash->pos++;
392
+ return NULL;
393
+ }
394
+
395
+ if (!obj && !info->obj->obj) {
396
+ /* a delete operation for an empty element */
397
+ return NULL;
398
+ }
399
+
400
+ /* an object exists, this is a "replace/delete" operation */
401
+ const void *old = (void *)info->obj->obj;
402
+
403
+ if (!obj) {
404
+ /* it was a delete operation */
405
+ if (info->obj == hash->ordered + hash->pos - 1) {
406
+ /* we removed the last ordered element, no need to keep any holes. */
407
+ --hash->pos;
408
+ FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
409
+ hash->ordered[hash->pos] =
410
+ (fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
411
+ *info = (fio_hash_data_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
412
+ if (hash->pos && !hash->ordered[hash->pos - 1].obj) {
413
+ fio_hash_pop(hash, NULL);
414
+ } else {
415
+ --hash->count;
416
+ }
417
+
418
+ return (void *)old;
419
+ }
420
+ --hash->count;
421
+ } else if (!old) {
422
+ /* inserted an item after a previous one was removed. */
423
+ ++hash->count;
424
+ }
425
+ info->obj->obj = obj;
426
+
427
+ return (void *)old;
428
+ }
429
+
430
+ /**
431
+ * Allows the Hash to be momenterally used as a stack, poping the last element
432
+ * entered.
433
+ * Remember that keys might have to be freed as well (`FIO_HASH_KEY_DESTROY`).
434
+ */
435
+ FIO_FUNC void *fio_hash_pop(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key) {
436
+ if (!hash->pos)
437
+ return NULL;
438
+ --(hash->pos);
439
+ --(hash->count);
440
+ void *old = hash->ordered[hash->pos].obj;
441
+ /* removing hole from hashtable is possible because it's the last element */
442
+ fio_hash_data_s *info =
443
+ fio_hash_seek_pos_(hash, hash->ordered[hash->pos].key);
444
+ if (!info) {
445
+ /* no info is a data corruption error. */
446
+ fprintf(stderr, "FATAL ERROR: (fio_hash) unexpected missing container.\n");
447
+ exit(-1);
448
+ }
449
+ *info = (fio_hash_data_s){.obj = NULL};
450
+ /* cleanup key (or copy to target) and reset the ordered position. */
451
+ if (key)
452
+ *key = hash->ordered[hash->pos].key;
453
+ else
454
+ FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
455
+ hash->ordered[hash->pos] =
456
+ (fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
457
+ /* remove any holes from the top (top is kept tight) */
458
+ while (hash->pos && hash->ordered[hash->pos - 1].obj == NULL) {
459
+ --(hash->pos);
460
+ info = fio_hash_seek_pos_(hash, hash->ordered[hash->pos].key);
461
+ if (!info) {
462
+ /* no info is a data corruption error. */
463
+ fprintf(stderr,
464
+ "FATAL ERROR: (fio_hash) unexpected missing container (2).\n");
465
+ exit(-1);
466
+ }
467
+ *info = (fio_hash_data_s){.obj = NULL};
468
+ FIO_HASH_KEY_DESTROY(hash->ordered[hash->pos].key);
469
+ hash->ordered[hash->pos] =
470
+ (fio_hash_data_ordered_s){.obj = NULL, .key = FIO_HASH_KEY_INVALID};
471
+ }
472
+ return old;
473
+ }
474
+
475
+ /**
476
+ * Allows a peak at the Hash's last element.
477
+ *
478
+ * If a pointer to `key` is provided, the element's key will be placed in it's
479
+ * place.
480
+ *
481
+ * Remember that keys might be destroyed if the Hash is altered
482
+ * (`FIO_HASH_KEY_DESTROY`).
483
+ */
484
+ FIO_FUNC void *fio_hash_last(fio_hash_s *hash, FIO_HASH_KEY_TYPE *key) {
485
+ if (key)
486
+ *key = hash->ordered[hash->pos - 1].key;
487
+ return hash->ordered[hash->pos - 1].obj;
488
+ }
489
+
490
+ /* attempts to rehash the hashmap. */
491
+ FIO_FUNC void fio_hash_rehash(fio_hash_s *h) {
492
+ if (!h->capa) /* lazy initialization */
493
+ h->mask = FIO_HASH_INITIAL_CAPACITY - 1;
494
+ retry_rehashing:
495
+ h->mask = ((h->mask) << 1) | 1;
496
+ {
497
+ /* It's better to reallocate using calloc than manually zero out memory */
498
+ /* Maybe there's enough zeroed out pages available in the system */
499
+ FIO_HASH_FREE(h->map, h->capa);
500
+ h->capa = h->mask + 1;
501
+ h->map = (fio_hash_data_s *)FIO_HASH_CALLOC(sizeof(*h->map), h->capa);
502
+ if (!h->map) {
503
+ perror("HashMap Allocation Failed");
504
+ exit(errno);
505
+ }
506
+ /* the ordered list doesn't care about initialized memory, so realloc */
507
+ /* will be faster. */
508
+ h->ordered = (fio_hash_data_ordered_s *)(FIO_HASH_REALLOC(
509
+ h->ordered, ((h->capa >> 1) * sizeof(*h->ordered)),
510
+ ((h->capa) * sizeof(*h->ordered)), ((h->pos) * sizeof(*h->ordered))));
511
+ if (!h->ordered) {
512
+ perror("HashMap Reallocation Failed");
513
+ exit(errno);
514
+ }
515
+ }
516
+ if (!h->count) {
517
+ /* empty hash */
518
+ return;
519
+
520
+ } else if (h->pos == h->count) {
521
+ /* the ordered list is fully occupied, no need to rearange. */
522
+ FIO_HASH_FOR_LOOP(h, i) {
523
+ /* can't use fio_hash_insert, because we're recycling containers */
524
+ fio_hash_data_s *place = fio_hash_seek_pos_(h, i->key);
525
+ if (!place) {
526
+ goto retry_rehashing;
527
+ }
528
+ *place = (fio_hash_data_s){.key = i->key, .obj = i};
529
+ }
530
+
531
+ } else {
532
+ /* the ordered list has holes, fill 'em up.*/
533
+ size_t reader = 0;
534
+ size_t writer = 0;
535
+ while (reader < h->pos) {
536
+ if (h->ordered[reader].obj) {
537
+ fio_hash_data_s *place = fio_hash_seek_pos_(h, h->ordered[reader].key);
538
+ if (!place) {
539
+ goto retry_rehashing;
540
+ }
541
+ *place = (fio_hash_data_s){.key = h->ordered[reader].key,
542
+ .obj = h->ordered + writer};
543
+ fio_hash_data_ordered_s old = h->ordered[reader];
544
+ h->ordered[reader] =
545
+ (fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
546
+ h->ordered[writer] = old;
547
+ ++writer;
548
+ } else {
549
+ FIO_HASH_KEY_DESTROY(h->ordered[reader].key);
550
+ h->ordered[reader].key = FIO_HASH_KEY_INVALID;
551
+ }
552
+ ++reader;
553
+ }
554
+ h->pos = writer;
555
+ // h->ordered[h->pos] =
556
+ // (fio_hash_data_ordered_s){.key = FIO_HASH_KEY_INVALID, .obj = NULL};
557
+ }
558
+ }
559
+
560
+ FIO_FUNC inline size_t fio_hash_each(fio_hash_s *hash, size_t start_at,
561
+ int (*task)(FIO_HASH_KEY_TYPE key,
562
+ void *obj, void *arg),
563
+ void *arg) {
564
+ if (start_at >= hash->count)
565
+ return hash->count;
566
+ size_t count = 0;
567
+ if (hash->pos == hash->count) {
568
+ count = start_at;
569
+ while (count < hash->pos) {
570
+ /* no "holes" in the hash. */
571
+ ++count;
572
+ if (task(hash->ordered[count - 1].key,
573
+ (void *)hash->ordered[count - 1].obj, arg) == -1)
574
+ return count;
575
+ }
576
+ } else {
577
+ size_t pos = 0;
578
+ while (count < start_at && pos < hash->pos) {
579
+ if (hash->ordered[pos].obj) {
580
+ ++count;
581
+ }
582
+ ++pos;
583
+ }
584
+ while (pos < hash->pos) {
585
+ if (hash->ordered[pos].obj) {
586
+ ++count;
587
+ if (task(hash->ordered[pos].key, (void *)hash->ordered[pos].obj, arg) ==
588
+ -1)
589
+ return count;
590
+ }
591
+ ++pos;
592
+ }
593
+ }
594
+ return count;
595
+ }
596
+
597
+ /** Returns the number of elements in the Hash. */
598
+ FIO_FUNC inline size_t fio_hash_count(const fio_hash_s *hash) {
599
+ if (!hash)
600
+ return 0;
601
+ return hash->count;
602
+ }
603
+
604
+ /**
605
+ * Returns a temporary theoretical Hash map capacity.
606
+ * This could be used for testig performance and memory consumption.
607
+ */
608
+ FIO_FUNC inline size_t fio_hash_capa(const fio_hash_s *hash) {
609
+ if (!hash)
610
+ return 0;
611
+ return hash->capa;
612
+ }
613
+
614
+ /**
615
+ * Attempts to minimize memory usage by removing empty spaces caused by deleted
616
+ * items and rehashing the Hash Map.
617
+ *
618
+ * Returns the updated hash map capacity.
619
+ */
620
+ FIO_FUNC inline size_t fio_hash_compact(fio_hash_s *hash) {
621
+ if (!hash)
622
+ return 0;
623
+ if (hash->count == hash->pos && (hash->count << 1) >= hash->capa)
624
+ return hash->capa;
625
+ /* compact ordered list */
626
+ {
627
+ size_t reader = 0;
628
+ size_t writer = 0;
629
+ while (reader < hash->pos) {
630
+ if (hash->ordered[reader].obj) {
631
+ hash->ordered[writer] = hash->ordered[reader];
632
+ ++writer;
633
+ } else {
634
+ FIO_HASH_KEY_DESTROY(hash->ordered[reader].key);
635
+ }
636
+ ++reader;
637
+ }
638
+ hash->pos = writer;
639
+ }
640
+ /* recalculate minimal length and rehash */
641
+ while (hash->mask && hash->mask >= hash->count)
642
+ hash->mask = hash->mask >> 1;
643
+ if (hash->mask + 1 < FIO_HASH_INITIAL_CAPACITY)
644
+ hash->mask = (FIO_HASH_INITIAL_CAPACITY - 1);
645
+ while (hash->count >= hash->mask)
646
+ hash->mask = (hash->mask << 1) | 1;
647
+ fio_hash_rehash(hash);
648
+
649
+ return hash->capa;
650
+ }
651
+
652
+ #if DEBUG && !FIO_HASH_NO_TEST
653
+ #define FIO_HASHMAP_TEXT_COUNT 524288UL
654
+ #include <stdio.h>
655
+ FIO_FUNC void fio_hash_test(void) {
656
+ #define TEST_ASSERT(cond, ...) \
657
+ if (!(cond)) { \
658
+ fprintf(stderr, "* " __VA_ARGS__); \
659
+ fprintf(stderr, "Testing failed.\n"); \
660
+ exit(-1); \
661
+ }
662
+ fio_hash_s h = {.capa = 0};
663
+ fprintf(stderr, "=== Testing Core HashMap (fio_hashmap.h)\n");
664
+ fprintf(stderr, "* Inserting %lu items\n", FIO_HASHMAP_TEXT_COUNT);
665
+ for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; ++i) {
666
+ fio_hash_insert(&h, i, (void *)i);
667
+ TEST_ASSERT((i == (uintptr_t)fio_hash_find(&h, i)), "insertion != find");
668
+ }
669
+ fprintf(stderr, "* Seeking %lu items\n", FIO_HASHMAP_TEXT_COUNT);
670
+ for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; ++i) {
671
+ TEST_ASSERT((i == (uintptr_t)fio_hash_find(&h, i)), "insertion != find");
672
+ }
673
+ {
674
+ fprintf(stderr, "* Testing order for %lu items\n", FIO_HASHMAP_TEXT_COUNT);
675
+ uintptr_t i = 1;
676
+ FIO_HASH_FOR_LOOP(&h, pos) {
677
+ TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
678
+ TEST_ASSERT(pos->key == i, "Key out of order %lu != %lu.",
679
+ (unsigned long)i, (unsigned long)pos->key);
680
+ ++i;
681
+ }
682
+ }
683
+ fprintf(stderr, "* Removing odd items from %lu items\n",
684
+ FIO_HASHMAP_TEXT_COUNT);
685
+ for (unsigned long i = 1; i < FIO_HASHMAP_TEXT_COUNT; i += 2) {
686
+ uintptr_t old = (uintptr_t)fio_hash_insert(&h, i, NULL);
687
+ TEST_ASSERT(old == i, "Removal didn't return old value.");
688
+ TEST_ASSERT(!(fio_hash_find(&h, i)), "Removal failed (still exists).");
689
+ }
690
+ if (1) {
691
+ size_t count = h.count;
692
+ size_t pos = h.pos;
693
+ fio_hash_insert(&h, 1, (void *)1);
694
+ TEST_ASSERT(
695
+ count + 1 == h.count,
696
+ "Readding a removed item should increase count by 1 (%zu + 1 != %zu).",
697
+ count, (size_t)h.count);
698
+ TEST_ASSERT(
699
+ pos == h.pos,
700
+ "Readding a removed item shouldn't change the position marker!");
701
+ TEST_ASSERT(fio_hash_find(&h, 1) == (void *)1,
702
+ "Readding a removed item should update the item (%p != 1)!",
703
+ fio_hash_find(&h, 1));
704
+ fio_hash_insert(&h, 1, NULL);
705
+ TEST_ASSERT(count == h.count,
706
+ "Re-removing an item should decrease count (%zu != %zu).",
707
+ count, (size_t)h.count);
708
+ TEST_ASSERT(pos == h.pos,
709
+ "Re-removing an item shouldn't effect the position marker!");
710
+ TEST_ASSERT(!fio_hash_find(&h, 1),
711
+ "Re-removing a re-added item should update the item!");
712
+ }
713
+ {
714
+ fprintf(stderr, "* Testing for %lu / 2 holes\n", FIO_HASHMAP_TEXT_COUNT);
715
+ uintptr_t i = 1;
716
+ FIO_HASH_FOR_LOOP(&h, pos) {
717
+ if (pos->obj) {
718
+ TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
719
+ TEST_ASSERT(pos->key == i, "Key out of order %lu != %lu.",
720
+ (unsigned long)i, (unsigned long)pos->key);
721
+ } else {
722
+ TEST_ASSERT(pos->obj == NULL, "old value detected.");
723
+ TEST_ASSERT(pos->key == i, "Key out of order.");
724
+ }
725
+ ++i;
726
+ }
727
+ }
728
+ {
729
+ fprintf(stderr, "* Poping two elements (testing pop through holes)\n");
730
+ FIO_HASH_KEY_TYPE k;
731
+ TEST_ASSERT(fio_hash_pop(&h, &k), "Pop 1 failed to collect object");
732
+ TEST_ASSERT(k, "Pop 1 failed to collect key");
733
+ FIO_HASH_KEY_DESTROY(k);
734
+ TEST_ASSERT(fio_hash_pop(&h, &k), "Pop 2 failed to collect object");
735
+ TEST_ASSERT(k, "Pop 2 failed to collect key");
736
+ FIO_HASH_KEY_DESTROY(k);
737
+ }
738
+ fprintf(stderr, "* Compacting Hash to %lu\n", FIO_HASHMAP_TEXT_COUNT >> 1);
739
+ fio_hash_compact(&h);
740
+ {
741
+ fprintf(stderr, "* Testing that %lu items are continues\n",
742
+ FIO_HASHMAP_TEXT_COUNT >> 1);
743
+ uintptr_t i = 0;
744
+ FIO_HASH_FOR_LOOP(&h, pos) {
745
+ TEST_ASSERT(pos->obj, "Found a hole after compact.");
746
+ TEST_ASSERT(pos->key == (uintptr_t)pos->obj, "Key and value mismatch.");
747
+ ++i;
748
+ }
749
+ TEST_ASSERT(i == h.count, "count error (%lu != %lu).", i, h.count);
750
+ }
751
+ fio_hash_free(&h);
752
+ fprintf(stderr, "* passed... without testing that FIO_HASH_KEY_DESTROY is "
753
+ "called only once.\n");
754
+ }
755
+ #endif /* DEBUG Testing */
756
+
757
+ #undef FIO_FUNC
758
+
759
+ #endif /* H_FIO_SIMPLE_HASH_H */