iodine 0.2.13 → 0.2.14

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc27a8990d89bbb19294133f6ec8178d2a586ea2
4
- data.tar.gz: 8e9ec06545307abd666902d434438679e5bdd7ad
3
+ metadata.gz: ccbe7057ea01a3ced6291824c722160e8566200c
4
+ data.tar.gz: ef231d216e71b62b57331d34161858c4b3fb0284
5
5
  SHA512:
6
- metadata.gz: a25fe9b7584dbeba407d55c1f150fdfab048fb7892a46ec32777d55b38ab3742faf0f7a972c118d3d6f26804380825324ff6f4b2665f91c56b0cb44f7f6c5ae6
7
- data.tar.gz: b0e1ca2d60baae702e1f953cd6b9e13566168ae4fe989e1d31e6e0fd37cbbfc8bf3c3dfcf1e4e83049e09c45ca369c9efd08779965562b17cd576308925573e7
6
+ metadata.gz: 00c4e4dfd7633df4fdf2f9ade456326e27671f8cb1f9dcdd0693ec6c2f1eb65a896f502c5e40065b155ee876b8f9e7be5446dccffa2b3d2cf5cf19ef52a888a4
7
+ data.tar.gz: 9153c0444bdb26d6f5d3864fb7986d9851e1793e61c53387fe8e2826e7de87d48aee369f37100ebf3b9fb92fede369e3f1e7293988c4606a358e7041d4822eb1
data/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ Please notice that this change log contains changes for upcoming releases as wel
8
8
 
9
9
  ***
10
10
 
11
+ Change log v.0.2.14
12
+
13
+ **Fix**: fixed the experimental `each_write`. An issue was found where passing a block might crash Iodine, since the block will be freed by the GC before Iodine was done with it. Now the block is correctly added to the object Registry, preventing premature memory deallocation.
14
+
15
+ **Fix**: fixed another issue with `each_write` where a race condition review was performed outside the protected critical section, in some cases this would caused memory to be freed twice and crash the server. This issue is now resolved.
16
+
17
+ **Deprecation**: In version 0.2.1 we have notified that the the websocket method `uuid` was deprecated in favor of `conn_id`, as suggested by the [Rack Websocket Draft](https://github.com/rack/rack/pull/1107). This deprecation is now enforced.
18
+
19
+ ***
20
+
11
21
  Change log v.0.2.13
12
22
 
13
23
  **Fix**: Fixed an issue presented in the C layer, where big fragmented websocket messages sent by the client could cause parsing errors and potentially, in some cases, cause a server thread to spin in a loop (DoS). Credit to @Filly for exposing the issue in the [`facil.io`](https://github.com/boazsegev/facil.io) layer. It should be noted that Chrome is the only browser where this issue could be invoked for testing.
data/bin/ws-shootout CHANGED
@@ -98,3 +98,5 @@ Iodine.start
98
98
  # ws.onclose = function(e) {console.log("closed")};
99
99
  # ws.onopen = function(e) {e.target.send("hi");};
100
100
  # };
101
+
102
+ # sleep 10 while `websocket-bench broadcast ws://127.0.0.1:3000/ --concurrent 10 --sample-size 100 --step-size 1000 --limit-percentile 95 --limit-rtt 250ms --initial-clients 1000`.tap {|s| puts s; puts "zzz..."}
data/exe/iodine CHANGED
@@ -16,8 +16,8 @@ Both <options> and <filename> are optional.
16
16
 
17
17
  Available options:
18
18
  -p Port number. Default: 3000.
19
- -t Number of threads. Default: 1 => single worker thread.
20
- -w Number of worker processes. Default: 1 => a single process.
19
+ -t Number of threads. Default: half of the CPU core count.
20
+ -w Number of worker processes. half of the CPU core count.
21
21
  -www Public folder for static file serving. Default: nil (none).
22
22
  -v Log responses.
23
23
  -q Never log responses.
@@ -109,7 +109,7 @@ If this isn't set manually, the first call to
109
109
  being written (which might be less then the total data sent, if the sending is
110
110
  fragmented).
111
111
 
112
- Set the value to -1 to force the HttpResponse not to write nor automate the
112
+ Set the value to -1 to force the HttpResponse not to write the
113
113
  `Content-Length` header.
114
114
  */
115
115
  ssize_t content_length;
@@ -252,6 +252,12 @@ static uint8_t iodine_ws_if_callback(ws_s *ws, void *block) {
252
252
  return ret && ret != Qnil && ret != Qfalse;
253
253
  }
254
254
 
255
+ static void iodine_ws_write_each_complete(ws_s *ws, void *block) {
256
+ (void)ws;
257
+ if ((VALUE)block != Qnil)
258
+ Registry.remove((VALUE)block);
259
+ }
260
+
255
261
  /**
256
262
  * Writes data to all the Websocket connections sharing the same process
257
263
  * (worker) except `self`.
@@ -276,10 +282,15 @@ static VALUE iodine_ws_multiwrite(VALUE self, VALUE data) {
276
282
  VALUE block = Qnil;
277
283
  if (rb_block_given_p())
278
284
  block = rb_block_proc();
279
- websocket_write_each(ws, RSTRING_PTR(data), RSTRING_LEN(data),
280
- rb_enc_get(data) == UTF8Encoding, 0,
281
- ((block == Qnil) ? NULL : iodine_ws_if_callback),
282
- (void *)block);
285
+ if (block != Qnil)
286
+ Registry.add(block);
287
+ websocket_write_each(.origin = ws, .data = RSTRING_PTR(data),
288
+ .length = RSTRING_LEN(data),
289
+ .is_text = (rb_enc_get(data) == UTF8Encoding),
290
+ .on_finished = iodine_ws_write_each_complete,
291
+ .filter =
292
+ ((block == Qnil) ? NULL : iodine_ws_if_callback),
293
+ .arg = (void *)block);
283
294
  return Qtrue;
284
295
  }
285
296
 
@@ -519,7 +530,7 @@ void Init_iodine_websocket(void) {
519
530
  rb_define_method(rWebsocket, "each_write", iodine_ws_multiwrite, 1);
520
531
  rb_define_method(rWebsocket, "close", iodine_ws_close, 0);
521
532
 
522
- rb_define_method(rWebsocket, "uuid", iodine_ws_uuid, 0);
533
+ // rb_define_method(rWebsocket, "uuid", iodine_ws_uuid, 0);
523
534
  rb_define_method(rWebsocket, "conn_id", iodine_ws_uuid, 0);
524
535
  rb_define_method(rWebsocket, "has_pending?", iodine_ws_has_pending, 0);
525
536
  rb_define_method(rWebsocket, "defer", iodine_defer, -1);
@@ -65,7 +65,7 @@ typedef union {
65
65
  } fduuid_u;
66
66
 
67
67
  #define FDUUID_FAIL(uuid) (uuid == -1)
68
- #define sock_uuid2fd(uuid) ((fduuid_u)(uuid)).data.fd
68
+ #define sock_uuid2fd(uuid) ((fduuid_u *)(&uuid))->data.fd
69
69
  #endif
70
70
 
71
71
  /*****************************/ /** \file
@@ -647,8 +647,9 @@ Sets a connection's timeout.
647
647
  Returns -1 on error (i.e. connection closed), otherwise returns 0.
648
648
  */
649
649
  void server_set_timeout(intptr_t fd, uint8_t timeout) {
650
- if (valid_uuid(fd) == 0)
650
+ if (valid_uuid(fd) == 0) {
651
651
  return;
652
+ }
652
653
  lock_uuid(fd);
653
654
  uuid_data(fd).active = server_data.last_tick;
654
655
  uuid_data(fd).timeout = timeout;
@@ -758,11 +759,11 @@ static inline srv_task_s *task_alloc(void) {
758
759
  }
759
760
 
760
761
  /* allows for later implementation of a task pool with minimal code updates. */
761
- static inline void task_free(srv_task_s *task) { return free(task); }
762
+ static inline void task_free(srv_task_s *task) { free(task); }
762
763
 
763
764
  /* performs a single connection task. */
764
765
  static void perform_single_task(void *task) {
765
- if (sock_isvalid(p2task(task).target) == 0) {
766
+ if (p2task(task).target < 0 || sock_isvalid(p2task(task).target) == 0) {
766
767
  if (p2task(task).on_finish) // an invalid connection fallback
767
768
  task2fallback(task)(p2task(task).origin, p2task(task).arg);
768
769
  task_free(task);
@@ -771,7 +772,7 @@ static void perform_single_task(void *task) {
771
772
  if (try_lock_uuid(p2task(task).target) == 0) {
772
773
  // get protocol
773
774
  protocol_s *protocol = protocol_uuid(p2task(task).target);
774
- if (protocol_set_busy(protocol) == 0) {
775
+ if (protocol && protocol_set_busy(protocol) == 0) {
775
776
  // clear the original busy flag
776
777
  unlock_uuid(p2task(task).target);
777
778
  p2task(task).task(p2task(task).target, protocol, p2task(task).arg);
@@ -871,7 +872,7 @@ void server_each(intptr_t origin_fd, const char *service,
871
872
  *t = (srv_task_s){.service = service,
872
873
  .origin = origin_fd,
873
874
  .task = task,
874
- .on_finish = on_finish,
875
+ .on_finish = (void *)on_finish,
875
876
  .arg = arg};
876
877
  if (async_run(perform_each_task, t))
877
878
  goto error;
@@ -895,8 +896,10 @@ void server_task(intptr_t caller_fd,
895
896
  t = task_alloc();
896
897
  if (t == NULL)
897
898
  goto error;
898
- *t = (srv_task_s){
899
- .target = caller_fd, .task = task, .on_finish = fallback, .arg = arg};
899
+ *t = (srv_task_s){.target = caller_fd,
900
+ .task = task,
901
+ .on_finish = (void *)fallback,
902
+ .arg = arg};
900
903
  if (async_run(perform_single_task, t))
901
904
  goto error;
902
905
  return;
data/ext/iodine/libsock.c CHANGED
@@ -194,12 +194,13 @@ static size_t fd_capacity = 0;
194
194
 
195
195
  #define uuid2info(uuid) fd_info[sock_uuid2fd(uuid)]
196
196
  #define is_valid(uuid) \
197
- (fd_info[sock_uuid2fd(uuid)].fduuid.data.counter == \
197
+ (sock_uuid2fd(uuid) >= 0 && sock_uuid2fd(uuid) <= (int)fd_capacity && \
198
+ fd_info[sock_uuid2fd(uuid)].fduuid.data.counter == \
198
199
  ((fduuid_u *)(&uuid))->data.counter && \
199
200
  uuid2info(uuid).open)
200
201
 
201
202
  static struct {
202
- sock_packet_s *pool;
203
+ sock_packet_s *volatile pool;
203
204
  sock_packet_s *allocated;
204
205
  spn_lock_i lock;
205
206
  } buffer_pool = {.lock = SPN_LOCK_INIT};
data/ext/iodine/spnlock.h CHANGED
@@ -62,13 +62,13 @@ typedef atomic_bool spn_lock_i;
62
62
  #define SPN_LOCK_INIT ATOMIC_VAR_INIT(0)
63
63
  /** returns 1 if the lock was busy (TRUE == FAIL). */
64
64
  static inline int spn_trylock(spn_lock_i *lock) {
65
- __asm__ volatile("" ::: "memory");
65
+ __sync_synchronize();
66
66
  return atomic_exchange(lock, 1);
67
67
  }
68
68
  /** Releases a lock. */
69
69
  static inline void spn_unlock(spn_lock_i *lock) {
70
70
  atomic_store(lock, 0);
71
- __asm__ volatile("" ::: "memory");
71
+ __sync_synchronize();
72
72
  }
73
73
  /** returns a lock's state (non 0 == Busy). */
74
74
  static inline int spn_is_locked(spn_lock_i *lock) { return atomic_load(lock); }
@@ -98,6 +98,7 @@ static inline int spn_trylock(spn_lock_i *lock) { return __sync_swap(lock, 1); }
98
98
  typedef volatile uint8_t spn_lock_i;
99
99
  /** returns 1 if the lock was busy (TRUE == FAIL). */
100
100
  static inline int spn_trylock(spn_lock_i *lock) {
101
+ __sync_synchronize();
101
102
  return __sync_fetch_and_or(lock, 1);
102
103
  }
103
104
  #define SPN_TMP_HAS_BUILTIN 1
@@ -116,6 +117,7 @@ typedef volatile uint8_t spn_lock_i;
116
117
  /** returns 1 if the lock was busy (TRUE == FAIL). */
117
118
  static inline int spn_trylock(spn_lock_i *lock) {
118
119
  spn_lock_i tmp;
120
+ __asm__ volatile("mfence" ::: "memory");
119
121
  __asm__ volatile("xchgb %0,%1" : "=r"(tmp), "=m"(*lock) : "0"(1) : "memory");
120
122
  return tmp;
121
123
  }
@@ -520,11 +520,12 @@ static size_t websocket_encode(void *buff, void *data, size_t len, char text,
520
520
  .size = len,
521
521
  .masked = client};
522
522
  memcpy(buff, &head, 2);
523
- if (client)
523
+ if (client) {
524
524
  websocket_mask((uint8_t *)buff + 2, data, len);
525
- else
525
+ len += 4;
526
+ } else
526
527
  memcpy((uint8_t *)buff + 2, data, len);
527
- return len + 2 + (client ? 4 : 0);
528
+ return len + 2;
528
529
  } else if (len < (1UL << 16)) {
529
530
  /* head is 4 bytes */
530
531
  struct {
@@ -542,11 +543,12 @@ static size_t websocket_encode(void *buff, void *data, size_t len, char text,
542
543
  .masked = client,
543
544
  .length = htons(len)};
544
545
  memcpy(buff, &head, 4);
545
- if (client)
546
+ if (client) {
546
547
  websocket_mask((uint8_t *)buff + 4, data, len);
547
- else
548
+ len += 4;
549
+ } else
548
550
  memcpy((uint8_t *)buff + 4, data, len);
549
- return len + 4 + (client ? 4 : 0);
551
+ return len + 4;
550
552
  }
551
553
  /* Really Long Message */
552
554
  struct {
@@ -565,11 +567,12 @@ static size_t websocket_encode(void *buff, void *data, size_t len, char text,
565
567
  };
566
568
  memcpy(buff, &head, 2);
567
569
  ((size_t *)((uint8_t *)buff + 2))[0] = bswap64(len);
568
- if (client)
570
+ if (client) {
569
571
  websocket_mask((uint8_t *)buff + 10, data, len);
570
- else
572
+ len += 4;
573
+ } else
571
574
  memcpy((uint8_t *)buff + 10, data, len);
572
- return len + 10 + (client ? 4 : 0);
575
+ return len + 10;
573
576
  }
574
577
 
575
578
  static void websocket_write_impl(intptr_t fd, void *data, size_t len,
@@ -817,23 +820,45 @@ Multi-Write (direct broadcast) Implementation
817
820
  */
818
821
  struct websocket_multi_write {
819
822
  uint8_t (*if_callback)(ws_s *ws_to, void *arg);
823
+ void (*on_finished)(ws_s *ws_origin, void *arg);
824
+ intptr_t origin;
820
825
  void *arg;
821
826
  spn_lock_i lock;
827
+ /* ... we need to have padding for pointer arithmatics... */
828
+ uint8_t as_client;
829
+ /* ... we need to have padding for pointer arithmatics... */
822
830
  size_t count;
823
831
  size_t length;
824
- uint8_t as_client;
825
- uint8_t buffer[];
832
+ uint8_t buffer[]; /* starts on border alignment */
826
833
  };
827
834
 
835
+ static void ws_mw_defered_on_finish_fb(intptr_t fd, void *arg) {
836
+ (void)(fd);
837
+ struct websocket_multi_write *fin = arg;
838
+ fin->on_finished(NULL, fin->arg);
839
+ free(fin);
840
+ }
841
+ static void ws_mw_defered_on_finish(intptr_t fd, protocol_s *ws, void *arg) {
842
+ (void)(fd);
843
+ struct websocket_multi_write *fin = arg;
844
+ fin->on_finished((ws->service == WEBSOCKET_ID_STR ? (ws_s *)ws : NULL),
845
+ fin->arg);
846
+ free(fin);
847
+ }
848
+
828
849
  static void ws_reduce_or_free_multi_write(void *buff) {
829
- struct websocket_multi_write *mw =
830
- (void *)((uintptr_t)buff - sizeof(struct websocket_multi_write));
850
+ struct websocket_multi_write *mw = (void *)((uintptr_t)buff - sizeof(*mw));
831
851
  spn_lock(&mw->lock);
832
852
  mw->count -= 1;
833
- spn_unlock(&mw->lock);
834
853
  if (!mw->count) {
835
- free(mw);
836
- }
854
+ spn_unlock(&mw->lock);
855
+ if (mw->on_finished) {
856
+ server_task(mw->origin, ws_mw_defered_on_finish, mw,
857
+ ws_mw_defered_on_finish_fb);
858
+ } else
859
+ free(mw);
860
+ } else
861
+ spn_unlock(&mw->lock);
837
862
  }
838
863
 
839
864
  static void ws_finish_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
@@ -872,20 +897,28 @@ static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
872
897
  ws_direct_multi_write(fd, _ws, arg);
873
898
  }
874
899
 
875
- void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
876
- uint8_t is_text, uint8_t as_client,
877
- uint8_t (*if_callback)(ws_s *ws_to, void *arg),
878
- void *arg) {
900
+ #undef websocket_write_each
901
+ int websocket_write_each(struct websocket_write_each_args_s args) {
902
+ if (!args.data || !args.length)
903
+ return -1;
879
904
  struct websocket_multi_write *multi =
880
- malloc(len + 14 /* max head size */ + sizeof(*multi));
881
- multi->length =
882
- websocket_encode(multi->buffer, data, len, is_text, 1, 1, as_client);
883
- multi->if_callback = if_callback;
884
- multi->arg = arg;
885
- multi->lock = SPN_LOCK_INIT;
886
- multi->count = 1;
887
- multi->as_client = as_client;
888
- server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
889
- (if_callback ? ws_check_multi_write : ws_direct_multi_write),
905
+ malloc(sizeof(*multi) + args.length + 16 /* max head size + 2 */);
906
+ if (!multi)
907
+ return -1;
908
+ *multi = (struct websocket_multi_write){
909
+ .length = websocket_encode(multi->buffer, args.data, args.length,
910
+ args.is_text, 1, 1, args.as_client),
911
+ .if_callback = args.filter,
912
+ .on_finished = args.on_finished,
913
+ .arg = args.arg,
914
+ .origin = (args.origin ? args.origin->fd : -1),
915
+ .as_client = args.as_client,
916
+ .lock = SPN_LOCK_INIT,
917
+ .count = 1,
918
+ };
919
+
920
+ server_each(multi->origin, WEBSOCKET_ID_STR,
921
+ (args.filter ? ws_check_multi_write : ws_direct_multi_write),
890
922
  multi, ws_finish_multi_write);
923
+ return 0;
891
924
  }
@@ -128,24 +128,44 @@ Performs a task on each websocket connection that shares the same process
128
128
  void websocket_each(ws_s *ws_originator,
129
129
  void (*task)(ws_s *ws_target, void *arg), void *arg,
130
130
  void (*on_finish)(ws_s *ws_originator, void *arg));
131
+
132
+ /**
133
+ The Arguments passed to the `websocket_write_each` function / macro are defined
134
+ here, for convinience of calling the function.
135
+ */
136
+ struct websocket_write_each_args_s {
137
+ /** The originating websocket client will be excluded from the `write`.
138
+ * Can be NULL. */
139
+ ws_s *origin;
140
+ /** The data to be written to the websocket - required(!) */
141
+ void *data;
142
+ /** The length of the data to be written to the websocket - required(!) */
143
+ size_t length;
144
+ /** Text mode vs. binary mode. Defaults to binary mode. */
145
+ uint8_t is_text;
146
+ /** Set to 1 to send the data to websockets where this application is the
147
+ * client. Defaults to 0 (the data is sent to all clients where this
148
+ * application is the server). */
149
+ uint8_t as_client;
150
+ /** A filter callback, allowing us to exclude some clients.
151
+ * Should return 1 to send data and 0 to exclude. */
152
+ uint8_t (*filter)(ws_s *ws_to, void *arg);
153
+ /** A callback called once all the data was sent. */
154
+ void (*on_finished)(ws_s *ws_to, void *arg);
155
+ /** A user specified argumernt passed to each of the callbacks. */
156
+ void *arg;
157
+ };
131
158
  /**
132
159
  Writes data to each websocket connection that shares the same process
133
160
  (except the originating `ws_s` connection which is allowed to be NULL).
134
161
 
135
- If an `if_callback` is provided, the data will be written to the connection only
136
- if the `if_callback` returns TRUE (a non zero value).
137
-
138
- The `as_client` is a boolean value indicating if the data should be masked (sent
139
- to a server, in client mode) or not. The data will only be sent to the
140
- connections matching the required state (i.e., if `as_client == 1`, the data
141
- will only be sent to connections where this process behaves as a websocket
142
- client). If some data should be sent in client mode and other in server mode,
143
- than the function must be called twice.
162
+ Accepts a sing `struct websocket_write_each_args_s` argument. See the struct
163
+ details for possible arguments.
144
164
  */
145
- void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
146
- uint8_t is_text, uint8_t as_client,
147
- uint8_t (*if_callback)(ws_s *ws_to, void *arg),
148
- void *arg); /**
165
+ int websocket_write_each(struct websocket_write_each_args_s args);
166
+ #define websocket_write_each(...) \
167
+ websocket_write_each((struct websocket_write_each_args_s){__VA_ARGS__})
168
+ /**
149
169
  Counts the number of websocket connections.
150
170
  */
151
171
  size_t websocket_count(ws_s *ws);
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.2.13'.freeze
2
+ VERSION = '0.2.14'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-25 00:00:00.000000000 Z
11
+ date: 2017-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack