agoo 2.0.5 → 2.1.0

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

Potentially problematic release.


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

@@ -9,12 +9,12 @@
9
9
 
10
10
  #include <ruby.h>
11
11
 
12
- #include "ccache.h"
13
12
  #include "hook.h"
14
13
  #include "log.h"
15
14
  #include "page.h"
16
15
  #include "queue.h"
17
16
  #include "sub.h"
17
+ #include "upgraded.h"
18
18
 
19
19
  typedef struct _Server {
20
20
  volatile bool inited;
@@ -27,12 +27,14 @@ typedef struct _Server {
27
27
  atomic_int running;
28
28
  pthread_t listen_thread;
29
29
  pthread_t con_thread;
30
-
30
+
31
+ pthread_mutex_t up_lock;
32
+ Upgraded up_list;
33
+
31
34
  struct _Queue con_queue;
32
35
  struct _Queue pub_queue;
33
36
  struct _Cache pages;
34
37
  struct _SubCache sub_cache; // subscription cache
35
- struct _CCache con_cache; // Only WebSocket and SSE connections
36
38
 
37
39
  Hook hooks;
38
40
  Hook hook404;
@@ -0,0 +1,51 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+
7
+ #include "debug.h"
8
+ #include "subject.h"
9
+
10
+ Subject
11
+ subject_create(const char *pattern, int plen) {
12
+ Subject subject = (Subject)malloc(sizeof(struct _Subject) - 7 + plen);
13
+
14
+ if (NULL != subject) {
15
+ DEBUG_ALLOC(mem_subject, subject);
16
+ subject->next = NULL;
17
+ memcpy(subject->pattern, pattern, plen);
18
+ subject->pattern[plen] = '\0';
19
+ }
20
+ return subject;
21
+ }
22
+
23
+ void
24
+ subject_destroy(Subject subject) {
25
+ DEBUG_FREE(mem_subject, subject);
26
+ free(subject);
27
+ }
28
+
29
+ bool
30
+ subject_check(Subject subj, const char *subject) {
31
+ const char *pat = subj->pattern;
32
+
33
+ for (; '\0' != *pat && '\0' != *subject; subject++) {
34
+ if (*subject == *pat) {
35
+ pat++;
36
+ } else if ('*' == *pat) {
37
+ for (; '\0' != *subject && '.' != *subject; subject++) {
38
+ }
39
+ if ('\0' == *subject) {
40
+ return true;
41
+ }
42
+ pat++;
43
+ } else if ('>' == *pat) {
44
+ return true;
45
+ } else {
46
+ break;
47
+ }
48
+ }
49
+ return '\0' == *pat && '\0' == *subject;
50
+ }
51
+
@@ -0,0 +1,17 @@
1
+ // Copyright (c) 2018, Peter Ohler, All rights reserved.
2
+
3
+ #ifndef __AGOO_SUBJECT_H__
4
+ #define __AGOO_SUBJECT_H__
5
+
6
+ #include <stdbool.h>
7
+
8
+ typedef struct _Subject {
9
+ struct _Subject *next;
10
+ char pattern[8];
11
+ } *Subject;
12
+
13
+ extern Subject subject_create(const char *pattern, int plen);
14
+ extern void subject_destroy(Subject subject);
15
+ extern bool subject_check(Subject subj, const char *subject);
16
+
17
+ #endif // __AGOO_SUBJECT_H__
@@ -23,6 +23,22 @@ text_create(const char *str, int len) {
23
23
  return t;
24
24
  }
25
25
 
26
+ Text
27
+ text_dup(Text t0) {
28
+ int len = t0->len;
29
+ Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
30
+
31
+ if (NULL != t) {
32
+ DEBUG_ALLOC(mem_text, t)
33
+ t->len = len;
34
+ t->alen = len;
35
+ t->bin = false;
36
+ atomic_init(&t->ref_cnt, 0);
37
+ memcpy(t->text, t0->text, len + 1);
38
+ }
39
+ return t;
40
+ }
41
+
26
42
  Text
27
43
  text_allocate(int len) {
28
44
  Text t = (Text)malloc(sizeof(struct _Text) - TEXT_MIN_SIZE + len + 1);
@@ -59,8 +75,9 @@ text_append(Text t, const char *s, int len) {
59
75
  if (t->alen <= t->len + len) {
60
76
  long new_len = t->alen + len + t->alen / 2;
61
77
  size_t size = sizeof(struct _Text) - TEXT_MIN_SIZE + new_len + 1;
78
+ #ifdef MEM_DEBUG
62
79
  Text t0 = t;
63
-
80
+ #endif
64
81
  if (NULL == (t = (Text)realloc(t, size))) {
65
82
  return NULL;
66
83
  }
@@ -17,6 +17,7 @@ typedef struct _Text {
17
17
  } *Text;
18
18
 
19
19
  extern Text text_create(const char *str, int len);
20
+ extern Text text_dup(Text t);
20
21
  extern Text text_allocate(int len);
21
22
  extern void text_ref(Text t);
22
23
  extern void text_release(Text t);
@@ -4,72 +4,221 @@
4
4
 
5
5
  #include <ruby/encoding.h>
6
6
 
7
+ #include "con.h"
8
+ #include "debug.h"
7
9
  #include "pub.h"
8
10
  #include "server.h"
11
+ #include "subject.h"
9
12
  #include "upgraded.h"
10
13
 
11
- static VALUE upgraded_mod = Qundef;
14
+ static VALUE upgraded_class = Qundef;
15
+
16
+ static VALUE sse_sym;
17
+ static VALUE websocket_sym;
12
18
 
13
- static ID cid_id = 0;
14
- static ID sid_id = 0;
15
19
  static ID on_open_id = 0;
16
20
  static ID to_s_id = 0;
17
21
 
22
+ static void
23
+ destroy(Upgraded up) {
24
+ Subject subject;
25
+
26
+ if (Qnil != up->wrap) {
27
+ DATA_PTR(up->wrap) = NULL;
28
+ up->wrap = Qnil;
29
+ }
30
+ if (NULL == up->prev) {
31
+ the_server.up_list = up->next;
32
+ if (NULL != up->next) {
33
+ up->next->prev = NULL;
34
+ }
35
+ } else {
36
+ up->prev->next = up->next;
37
+ if (NULL != up->next) {
38
+ up->next->prev = up->prev;
39
+ }
40
+ }
41
+ while (NULL != (subject = up->subjects)) {
42
+ up->subjects = up->subjects->next;
43
+ subject_destroy(subject);
44
+ }
45
+ DEBUG_FREE(mem_upgraded, up);
46
+ free(up);
47
+ }
48
+
49
+ void
50
+ upgraded_release(Upgraded up) {
51
+ pthread_mutex_lock(&the_server.up_lock);
52
+ if (atomic_fetch_sub(&up->ref_cnt, 1) <= 1) {
53
+ destroy(up);
54
+ }
55
+ pthread_mutex_unlock(&the_server.up_lock);
56
+ }
57
+
58
+ void
59
+ upgraded_release_con(Upgraded up) {
60
+ pthread_mutex_lock(&the_server.up_lock);
61
+ up->con = NULL;
62
+ if (atomic_fetch_sub(&up->ref_cnt, 1) <= 1) {
63
+ destroy(up);
64
+ }
65
+ pthread_mutex_unlock(&the_server.up_lock);
66
+ }
67
+
68
+ // Called from the con_loop thread, no need to lock, this steals the subject
69
+ // so the pub subject should set to NULL
70
+ void
71
+ upgraded_add_subject(Upgraded up, Subject subject) {
72
+ Subject s;
73
+
74
+ for (s = up->subjects; NULL != s; s = s->next) {
75
+ if (0 == strcmp(subject->pattern, s->pattern)) {
76
+ subject_destroy(subject);
77
+ return;
78
+ }
79
+ }
80
+ subject->next = up->subjects;
81
+ up->subjects = subject;
82
+ }
83
+
84
+ void
85
+ upgraded_del_subject(Upgraded up, Subject subject) {
86
+ if (NULL == subject) {
87
+ while (NULL != (subject = up->subjects)) {
88
+ up->subjects = up->subjects->next;
89
+ subject_destroy(subject);
90
+ }
91
+ } else {
92
+ Subject s;
93
+ Subject prev = NULL;
94
+
95
+ for (s = up->subjects; NULL != s; s = s->next) {
96
+ if (0 == strcmp(subject->pattern, s->pattern)) {
97
+ if (NULL == prev) {
98
+ up->subjects = s->next;
99
+ } else {
100
+ prev->next = s->next;
101
+ }
102
+ subject_destroy(s);
103
+ break;
104
+ }
105
+ prev = s;
106
+ }
107
+ }
108
+ }
109
+
110
+ bool
111
+ upgraded_match(Upgraded up, const char *subject) {
112
+ Subject s;
113
+
114
+ for (s = up->subjects; NULL != s; s = s->next) {
115
+ if (subject_check(s, subject)) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+
122
+ void
123
+ upgraded_ref(Upgraded up) {
124
+ atomic_fetch_add(&up->ref_cnt, 1);
125
+ }
126
+
127
+ static Upgraded
128
+ get_upgraded(VALUE self) {
129
+ Upgraded up = NULL;
130
+
131
+ if (the_server.active) {
132
+ pthread_mutex_lock(&the_server.up_lock);
133
+ if (NULL != (up = DATA_PTR(self))) {
134
+ atomic_fetch_add(&up->ref_cnt, 1);
135
+ }
136
+ pthread_mutex_unlock(&the_server.up_lock);
137
+ }
138
+ return up;
139
+ }
140
+
18
141
  /* Document-method: write
19
142
  *
20
143
  * call-seq: write(msg)
21
144
  *
22
- * Writes a message to the WebSocket or SSe connection.
145
+ * Writes a message to the WebSocket or SSE connection. Returns true if the
146
+ * message has been queued and false otherwise. A closed connection or too
147
+ * many pending messages could cause a value of false to be returned.
23
148
  */
24
149
  static VALUE
25
150
  up_write(VALUE self, VALUE msg) {
26
- uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
151
+ Upgraded up = get_upgraded(self);
27
152
  Pub p;
28
153
 
29
- if (!the_server.active) {
30
- rb_raise(rb_eIOError, "Server shutdown.");
154
+ if (NULL == up) {
155
+ return Qfalse;
31
156
  }
32
- if (0 < the_server.max_push_pending) {
33
- int pending = cc_get_pending(&the_server.con_cache, cid);
34
-
35
- if (the_server.max_push_pending <= pending) {
36
- rb_raise(rb_eIOError, "Too many writes pending. Try again later.");
37
- }
157
+ if (0 < the_server.max_push_pending && the_server.max_push_pending <= atomic_load(&up->pending)) {
158
+ atomic_fetch_sub(&up->ref_cnt, 1);
159
+ // Too many pending messages.
160
+ return Qfalse;
38
161
  }
39
162
  if (T_STRING == rb_type(msg)) {
40
163
  if (RB_ENCODING_IS_ASCII8BIT(msg)) {
41
- p = pub_write(cid, StringValuePtr(msg), RSTRING_LEN(msg), true);
164
+ p = pub_write(up, StringValuePtr(msg), RSTRING_LEN(msg), true);
42
165
  } else {
43
- p = pub_write(cid, StringValuePtr(msg), RSTRING_LEN(msg), false);
166
+ p = pub_write(up, StringValuePtr(msg), RSTRING_LEN(msg), false);
44
167
  }
45
168
  } else {
46
169
  volatile VALUE rs = rb_funcall(msg, to_s_id, 0);
47
170
 
48
- p = pub_write(cid, StringValuePtr(rs), RSTRING_LEN(rs), false);
171
+ p = pub_write(up, StringValuePtr(rs), RSTRING_LEN(rs), false);
49
172
  }
50
- cc_pending_inc(&the_server.con_cache, cid);
173
+ atomic_fetch_add(&up->pending, 1);
51
174
  queue_push(&the_server.pub_queue, p);
52
175
 
53
- return Qnil;
176
+ return Qtrue;
54
177
  }
55
178
 
56
179
  /* Document-method: subscribe
57
180
  *
58
181
  * call-seq: subscribe(subject)
59
182
  *
60
- * Subscribes to messages published on the specified subject. (not implemented
61
- * yet)
183
+ * Subscribes to messages published on the specified subject. The subject is a
184
+ * dot delimited string that can include a '*' character as a wild card that
185
+ * matches any set of characters. The '>' character matches all remaining
186
+ * characters. Examples: people.fred.log, people.*.log, people.fred.>
62
187
  */
63
188
  static VALUE
64
189
  up_subscribe(VALUE self, VALUE subject) {
190
+ Upgraded up;
191
+
192
+ rb_check_type(subject, T_STRING);
193
+ if (NULL != (up = get_upgraded(self))) {
194
+ atomic_fetch_add(&up->pending, 1);
195
+ queue_push(&the_server.pub_queue, pub_subscribe(up, StringValuePtr(subject), RSTRING_LEN(subject)));
196
+ }
197
+ return Qnil;
198
+ }
65
199
 
66
- // printf("*** subscribe called\n");
67
-
68
- // increment _sid
69
- // create subscription
70
- // push pub onto server pub_queue
71
- // TBD create subscription object and return it
200
+ /* Document-method: unsubscribe
201
+ *
202
+ * call-seq: unsubscribe(subject=nil)
203
+ *
204
+ * Unsubscribes to messages on the provided subject. If the subject is nil
205
+ * then all subscriptions for the object are removed.
206
+ */
207
+ static VALUE
208
+ up_unsubscribe(int argc, VALUE *argv, VALUE self) {
209
+ Upgraded up;
210
+ const char *subject = NULL;
211
+ int slen = 0;
72
212
 
213
+ if (0 < argc) {
214
+ rb_check_type(argv[0], T_STRING);
215
+ subject = StringValuePtr(argv[0]);
216
+ slen = (int)RSTRING_LEN(argv[0]);
217
+ }
218
+ if (NULL != (up = get_upgraded(self))) {
219
+ atomic_fetch_add(&up->pending, 1);
220
+ queue_push(&the_server.pub_queue, pub_unsubscribe(up, subject, slen));
221
+ }
73
222
  return Qnil;
74
223
  }
75
224
 
@@ -81,13 +230,12 @@ up_subscribe(VALUE self, VALUE subject) {
81
230
  */
82
231
  static VALUE
83
232
  up_close(VALUE self) {
84
- uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
233
+ Upgraded up = get_upgraded(self);
85
234
 
86
- if (!the_server.active) {
87
- rb_raise(rb_eIOError, "Server shutdown.");
235
+ if (NULL != up) {
236
+ atomic_fetch_add(&up->pending, 1);
237
+ queue_push(&the_server.pub_queue, pub_close(up));
88
238
  }
89
- queue_push(&the_server.pub_queue, pub_close(cid));
90
-
91
239
  return Qnil;
92
240
  }
93
241
 
@@ -100,49 +248,108 @@ up_close(VALUE self) {
100
248
  */
101
249
  static VALUE
102
250
  pending(VALUE self) {
103
- uint64_t cid = RB_FIX2ULONG(rb_ivar_get(self, cid_id));
251
+ Upgraded up = get_upgraded(self);
252
+ int pending = -1;
253
+
254
+ if (NULL != up) {
255
+ pending = atomic_load(&up->pending);
256
+ atomic_fetch_sub(&up->ref_cnt, 1);
257
+ }
258
+ return INT2NUM(pending);
259
+ }
104
260
 
105
- if (!the_server.active) {
106
- rb_raise(rb_eIOError, "Server shutdown.");
261
+ /* Document-method: protocol
262
+ *
263
+ * call-seq: protocol()
264
+ *
265
+ * Returns the protocol of the upgraded connection as either :websocket or
266
+ * :sse. If not longer connected nil is returned.
267
+ */
268
+ static VALUE
269
+ protocol(VALUE self) {
270
+ VALUE pro = Qnil;
271
+
272
+ if (the_server.active) {
273
+ Upgraded up;
274
+
275
+ pthread_mutex_lock(&the_server.up_lock);
276
+ if (NULL != (up = DATA_PTR(self)) && NULL != up->con) {
277
+ switch (up->con->kind) {
278
+ case CON_WS:
279
+ pro = websocket_sym;
280
+ break;
281
+ case CON_SSE:
282
+ pro = sse_sym;
283
+ break;
284
+ default:
285
+ break;
286
+ }
287
+ }
288
+ pthread_mutex_unlock(&the_server.up_lock);
107
289
  }
108
- return INT2NUM(cc_get_pending(&the_server.con_cache, cid));
290
+ return pro;
109
291
  }
110
292
 
111
- void
112
- upgraded_extend(uint64_t cid, VALUE obj) {
293
+ Upgraded
294
+ upgraded_create(Con c, VALUE obj) {
295
+ Upgraded up = (Upgraded)malloc(sizeof(struct _Upgraded));
296
+
113
297
  if (!the_server.active) {
114
298
  rb_raise(rb_eIOError, "Server shutdown.");
115
299
  }
116
- rb_ivar_set(obj, cid_id, ULONG2NUM(cid));
117
- rb_ivar_set(obj, sid_id, ULONG2NUM(0)); // used as counter for subscription id
118
- rb_extend_object(obj, upgraded_mod);
119
- if (rb_respond_to(obj, on_open_id)) {
120
- rb_funcall(obj, on_open_id, 0);
300
+ if (NULL != up) {
301
+ DEBUG_ALLOC(mem_upgraded, up);
302
+ up->con = c;
303
+ up->handler = obj;
304
+ atomic_init(&up->pending, 0);
305
+ atomic_init(&up->ref_cnt, 1); // start with 1 for the Con reference
306
+ up->on_empty = rb_respond_to(obj, rb_intern("on_drained"));
307
+ up->on_close = rb_respond_to(obj, rb_intern("on_close"));
308
+ up->on_shut = rb_respond_to(obj, rb_intern("on_shutdown"));
309
+ up->on_msg = rb_respond_to(obj, rb_intern("on_message"));
310
+ up->wrap = Data_Wrap_Struct(upgraded_class, NULL, NULL, up);
311
+ up->subjects = NULL;
312
+ up->prev = NULL;
313
+ pthread_mutex_lock(&the_server.up_lock);
314
+ if (NULL == the_server.up_list) {
315
+ up->next = NULL;
316
+ } else {
317
+ the_server.up_list->prev = up;
318
+ }
319
+ up->next = the_server.up_list;
320
+ the_server.up_list = up;
321
+ c->up = up;
322
+ pthread_mutex_unlock(&the_server.up_lock);
323
+
324
+ if (rb_respond_to(obj, on_open_id)) {
325
+ rb_funcall(obj, on_open_id, 1, up->wrap);
326
+ }
121
327
  }
122
- cc_set_handler(&the_server.con_cache, cid, obj,
123
- rb_respond_to(obj, rb_intern("on_drained")),
124
- rb_respond_to(obj, rb_intern("on_close")),
125
- rb_respond_to(obj, rb_intern("on_shutdown")),
126
- rb_respond_to(obj, rb_intern("on_message")));
127
- rb_funcall(obj, rb_intern("to_s"), 0);
328
+ return up;
128
329
  }
129
330
 
331
+ // Use the publish from the Agoo module.
332
+ extern VALUE ragoo_publish(VALUE self, VALUE subject, VALUE message);
333
+
130
334
  /* Document-module: Agoo::Upgraded
131
335
  *
132
- * Adds methods to a handler of WebSocket and SSe connections.
336
+ * Adds methods to a handler of WebSocket and SSE connections.
133
337
  */
134
338
  void
135
339
  upgraded_init(VALUE mod) {
136
- upgraded_mod = rb_define_module_under(mod, "Upgraded");
340
+ upgraded_class = rb_define_class_under(mod, "Upgraded", rb_cObject);
137
341
 
138
- rb_define_method(upgraded_mod, "write", up_write, 1);
139
- rb_define_method(upgraded_mod, "subscribe", up_subscribe, 1);
140
- rb_define_method(upgraded_mod, "close", up_close, 0);
141
- rb_define_method(upgraded_mod, "pending", pending, 0);
342
+ rb_define_method(upgraded_class, "write", up_write, 1);
343
+ rb_define_method(upgraded_class, "subscribe", up_subscribe, 1);
344
+ rb_define_method(upgraded_class, "unsubscribe", up_unsubscribe, -1);
345
+ rb_define_method(upgraded_class, "close", up_close, 0);
346
+ rb_define_method(upgraded_class, "pending", pending, 0);
347
+ rb_define_method(upgraded_class, "protocol", protocol, 0);
348
+ rb_define_method(upgraded_class, "publish", ragoo_publish, 2);
142
349
 
143
- cid_id = rb_intern("_cid");
144
- sid_id = rb_intern("_sid");
145
350
  on_open_id = rb_intern("on_open");
146
351
  to_s_id = rb_intern("to_s");
147
-
352
+
353
+ sse_sym = ID2SYM(rb_intern("sse")); rb_gc_register_address(&sse_sym);
354
+ websocket_sym = ID2SYM(rb_intern("websocket")); rb_gc_register_address(&websocket_sym);
148
355
  }