iodine 0.7.14 → 0.7.15

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
  SHA256:
3
- metadata.gz: 0d3f8b237b265c8151aa3fe966a9c863fa3c053e4fd97289cfe498b77818e564
4
- data.tar.gz: 389b80f9feff3abf97ba7fdaf08d0a2986521093fb0365ab10c5e4b3af3b2de5
3
+ metadata.gz: f1996d0474dc00ad0614035f36b703154b594415278cd18624cec94c8149150e
4
+ data.tar.gz: 3060659e07df3fc734594584b407b5bf9156ae77fcdb8709f62a49d0f2f62296
5
5
  SHA512:
6
- metadata.gz: d8b220410c1ebdecf221bde09d48f64ed0c69dc50a0f0ecba69a3e530dc2b9cace643c9a1d8bc7ce73ecf9f1274ffc1162a21db6db273673de84318f9ea81cb3
7
- data.tar.gz: 94f71023813326cb0edfabecbbd862e566ec37882de35ca6b6f14a2759088b54ed52df9bb943888b3c58f3441881c67a4c82197550272593050eaca6fc66b3e8
6
+ metadata.gz: 2ab1733a97c6617232022405bd25d4f0c46d4a99c6751b5a8467e48fa17efc0dc815dc47ca329c7ca955d135c56e8a06c69d3fd168ead0b3510712e909711468
7
+ data.tar.gz: 53be4c7971bca24fd484bfa4dd18d67a99ffba196a5908c76bf1a3001a957ef607c51ec8d90b3d4714d6297609bf4dde8077d24bd85eee081b875bf27faa5d8f
@@ -6,6 +6,18 @@ Please notice that this change log contains changes for upcoming releases as wel
6
6
 
7
7
  ## Changes:
8
8
 
9
+ #### Change log v.0.7.15
10
+
11
+ **Fix**: (`fio`) fixed a minor memory leak in cluster mode, caused by the root process not freeing the hash map used for child process subscription monitoring (only effected hot restarts).
12
+
13
+ **Fix**: (`fio`) fixed superfluous and potentially erroneous pub/sub engine callback calls to `unsubscribe`, caused by (mistakingly) reporting filter channel closure.
14
+
15
+ **Fix**: (`http/1.1`) avoid processing further requests if the connection was closed.
16
+
17
+ **Fix**: (`iodine`) fixed some errors in the documentation and added a missing deprecation notice.
18
+
19
+ **Update**: (`fio`) updated the automatic concurrency calculations to leave resources for the system when a negative value is provided (was only available for worker count calculations, now available for thread count as well).
20
+
9
21
  #### Change log v.0.7.14
10
22
 
11
23
  **Fix**: (`facil.io`) fixed superfluous ping event.
data/README.md CHANGED
@@ -507,25 +507,26 @@ require 'iodine'
507
507
 
508
508
  # an echo protocol with asynchronous notifications.
509
509
  class EchoProtocol
510
- # `on_message` is an optional alternative to the `on_data` callback.
511
- # `on_message` has a 1Kb buffer that recycles itself for memory optimization.
510
+ # `on_message` is called when data is available.
512
511
  def on_message client, buffer
513
512
  # writing will never block and will use a buffer written in C when needed.
514
513
  client.write buffer
515
514
  # close will be performed only once all the data in the write buffer
516
515
  # was sent. use `force_close` to close early.
517
516
  client.close if buffer =~ /^bye[\r\n]/i
518
- # run asynchronous tasks using the thread pool
519
- Iodine.run do
520
- sleep 1
521
- puts "Echoed data: #{buffer}"
517
+ # run asynchronous tasks... after a set number of milliseconds
518
+ Iodine.run_after(1000) do
519
+ # or schedule the task immediately
520
+ Iodine.run do
521
+ puts "Echoed data: #{buffer}"
522
+ end
522
523
  end
523
524
  end
524
525
  end
525
526
 
526
527
  # listen on port 3000 for the echo protocol.
527
528
  Iodine.listen(port: "3000") { EchoProtocol.new }
528
- Iodine.threads = 4
529
+ Iodine.threads = 1
529
530
  Iodine.workers = 1
530
531
  Iodine.start
531
532
  ```
@@ -565,7 +566,7 @@ class ChatProtocol
565
566
  end
566
567
  end
567
568
 
568
- #an initial login protocol
569
+ # an initial login protocol
569
570
  class LoginProtocol
570
571
  def on_open client
571
572
  client.write "Enter nickname to log in to chat room:\n"
@@ -20,7 +20,7 @@ Conforming Pub/Sub implementations **MUST** implement the following pub/sub rela
20
20
 
21
21
  * `:match` indicates a matching algorithm should be applied to the `to` variable (`to` is a pattern).
22
22
 
23
- Possible values should include [`:redis`](https://github.com/antirez/redis/blob/398b2084af067ae4d669e0ce5a63d3bc89c639d3/src/util.c#L46-L167), [`:nats`](https://nats.io/documentation/faq/#wildcards) or [`:rabbitmq`](https://www.rabbitmq.com/tutorials/tutorial-five-ruby.html). Pub/Sub implementations *MAY* support none, some or all of these common pattern resolution schemes.
23
+ Possible (suggested) values should include [`:redis`](https://github.com/antirez/redis/blob/398b2084af067ae4d669e0ce5a63d3bc89c639d3/src/util.c#L46-L167), [`:nats`](https://nats.io/documentation/faq/#wildcards) or [`:rabbitmq`](https://www.rabbitmq.com/tutorials/tutorial-five-ruby.html). Pub/Sub implementations *MAY* support none, some or all of these common pattern resolution schemes.
24
24
 
25
25
  * `:handler` is an alternative to the optional block. It should accept Proc like objects (objects that answer to `.call(from, msg)`).
26
26
 
@@ -31,7 +31,9 @@ Conforming Pub/Sub implementations **MUST** implement the following pub/sub rela
31
31
  This option is only valid if the optional `block` is missing and the connection is a WebSocket connection. Note that SSE connections are limited to text data by design.
32
32
 
33
33
  This will dictate the encoding for outgoing WebSocket message when publications are directly sent to the client (as a text message or a binary blob). `:text` will be the default value for a missing `:as` option.
34
-
34
+
35
+ Servers *MAY* ignore this value if they set the message type (text/binary) based on UTF-8 validation.
36
+
35
37
  If a subscription to `to` already exists, it should be *replaced* by the new subscription (the old subscription should be canceled / unsubscribed).
36
38
 
37
39
  When the `subscribe` method is called within a WebSocket / SSE Callback object, the subscription must be closed automatically when the connection is closed.
@@ -50,64 +52,66 @@ Conforming Pub/Sub implementations **MUST** implement the following pub/sub rela
50
52
 
51
53
  * `message` a String with containing the data to be published.
52
54
 
53
- * `engine` (optional) routed the publish method to the specified Pub/Sub Engine (see later on). If none is specified, the default engine should be used.
55
+ * `engine` routes the publish method to the specified Pub/Sub Engine (see later on). If none is specified, the default engine should be used. If `false` is specified, the message should be forwarded to all subscribed clients.
54
56
 
55
57
  The `publish` method must return `true` if a publication was scheduled (not necessarily performed). If it's already known that the publication would fail, the method should return `false`.
56
58
 
57
59
  An implementation **MUST** call the relevant PubSubEngine's `publish` method after performing any internal book keeping logic. If `engine` is `nil`, the default PubSubEngine should be called. If `engine` is `false`, the implementation **MUST** forward the published message to the actual clients (if any).
58
60
 
59
- A global alias for this method (allowing it to be accessed from outside active connections) should be defined as `Rack::PubSub.publish`.
61
+ A global alias for this method (allowing it to be accessed from outside active connections) **MAY** be defined as `Rack::PubSub.publish`.
60
62
 
61
- Implementations **MUST** implement the following methods:
63
+ Implementations **MUST** implement the following methods in one of their public classes / modules (iodine implements these under `Iodine::PubSub`):
62
64
 
63
- * `Rack::PubSub.register(engine)` where `engine` is a PubSubEngine object as described in this specification.
65
+ * `attach(engine)` where `engine` is a `PubSubEngine` object, as described in this specification.
64
66
 
65
- When a pub/sub engine is registered, the implementation **MUST** inform the engine of any existing or future subscriptions.
67
+ When a pub/sub engine is attached, the implementation **MUST** inform the engine of any existing or future subscriptions.
66
68
 
67
69
  The implementation **MUST** call the engine's `subscribe` callback for each existing (and future) subscription.
68
70
 
69
- * `Rack::PubSub.deregister(engine)` where `engine` is a PubSubEngine object as described in this specification.
71
+ The implementation **MUST** allow multiple "engines" to be attached when multiple calls to `attach` are made.
72
+
73
+ * `detach(engine)` where `engine` is a PubSubEngine object as described in this specification.
70
74
 
71
- Removes an engine from the pub/sub registration. The opposit of
75
+ Removes an engine from the pub/sub system. The opposite of `attach`.
72
76
 
73
- * `Rack::PubSub.default_engine = engine` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
77
+ * `default = engine` sets a default pub/sub engine, where `engine` is a PubSubEngine object as described in this specification.
74
78
 
75
- Implementations **MUST** forward any `publish` method calls to the default pub/sub engine.
79
+ Implementations **MUST** forward any `publish` method calls to the default pub/sub engine, unless an `engine` is specified in arguments passes to the `publish` method.
76
80
 
77
- * `Rack::PubSub.default_engine` returns the current default pub/sub engine, where the engine is a PubSubEngine object as described in this specification.
81
+ * `default` returns the current default pub/sub engine, where the engine is a PubSubEngine object as described in this specification.
78
82
 
79
- * `Rack::PubSub.reset(engine)` where `engine` is a PubSubEngine object as described in this specification.
83
+ * `reset(engine)` where `engine` is a PubSubEngine object as described in this specification.
80
84
 
81
85
  Implementations **MUST** behave as if the engine was newly registered and (re)inform the engine of any existing subscriptions by calling engine's `subscribe` callback for each existing subscription.
82
86
 
83
- Implementations **MAY** implement pub/sub internally (in which case the `pubsub_default` engine is the server itself or a server's module).
87
+ Implementations **MAY** implement pub/sub internally (in which case the `default` engine is the server itself or a server's module).
84
88
 
85
89
  However, servers **MUST** support external pub/sub "engines" as described above, using PubSubEngine objects.
86
90
 
87
- PubSubEngine objects **MUST** implement the following methods:
91
+ `PubSubEngine` objects **MUST** implement the following methods:
88
92
 
89
- * `subscribe(channel, as=nil)` this method performs the subscription to the specified channel.
93
+ * `subscribe(channel, match=nil)` this method performs the subscription to the specified channel.
90
94
 
91
- If `as` is a Symbol that the engine recognizes (i.e., `:redis`, `:nats`, etc'), the engine should behave accordingly. i.e., the value `:redis` on a Redis engine will invoke the PSUBSCRIBE Redis command.
95
+ If `match` is a Symbol that the engine recognizes (i.e., `:redis`, `:nats`, etc'), the engine should behave accordingly. i.e., the value `:redis` on a Redis engine will invoke the PSUBSCRIBE Redis command.
92
96
 
93
97
  The method must return `true` if a subscription was scheduled (or performed) or `false` if the subscription is known to fail.
94
98
 
95
99
  This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
96
100
 
97
- * `unsubscribe(channel, as=nil)` this method performs closes the subscription to the specified channel.
101
+ * `unsubscribe(channel, match=nil)` this method performs closes the subscription to the specified channel.
98
102
 
99
103
  The method's semantics are similar to `subscribe`.
100
104
 
101
105
  This method will be called by the server (for each registered engine). The engine may assume that the method would never be called directly by an application.
102
106
 
103
- * `publish(channel, message)` where both `channel` and `message` are String object.
107
+ * `publish(channel, message)` where both `channel` and `message` are String objects.
104
108
 
105
109
  This method will be called by the server when a message is published using the engine.
106
110
 
107
- The engine **MUST** assume that the method might called directly by an application.
111
+ The engine **MUST** assume that the method might get called directly by an application.
108
112
 
109
- When a PubSubEngine object receives a published message, it should call:
113
+ When a PubSubEngine object receives a published message, it *should* call:
110
114
 
111
115
  ```ruby
112
- Rack::PubSub.publish channel: channel, message: message, engine: false
116
+ Foo::PubSub.publish channel, message, false
113
117
  ```
@@ -13,9 +13,9 @@ The purpose of these specifications is:
13
13
 
14
14
  Simply put, when choosing between conforming servers, the application doesn’t need to have any knowledge about the chosen server.
15
15
 
16
- 2. To Support “native" (server-side) WebSocket and EventSource (SSE) connections and using application side callbacks.
16
+ 2. To support “native" (server-side) WebSocket and EventSource (SSE) connections and using application side callbacks.
17
17
 
18
- Simply put, to make it easy for applications to accept WebSocket and EventSource (SSE) connections from WebSocket and EventSource clients (commonly browsers).
18
+ Simply put, to make it easy for applications to accept WebSocket and EventSource (SSE) connections from WebSocket and EventSource clients (commonly browsers) while abstracting away any transport layer details.
19
19
 
20
20
  3. Allow applications to use WebSocket and EventSource (SSE) on HTTP/2 servers. Note: current `hijack` practices will break network connections when attempting to implement EventSource (SSE).
21
21
 
@@ -45,11 +45,11 @@ The Callback Object could be a any object which implements any of the following
45
45
 
46
46
  Servers **MAY**, optionally, implement a **recyclable buffer** for the `on_message` callback. However, this is optional and it is *not* required.
47
47
 
48
- * `on_drained(client)` **MAY** be called when the the `write` buffer becomes empty. **If** `pending` returns a non-zero value, the `on_drained` callback **MUST** be called once the write buffer becomes empty.
48
+ * `on_drained(client)` **MAY** be called when the the `client.write` buffer becomes empty. **If** `client.pending` returns a non-zero value, the `on_drained` callback **MUST** be called once the write buffer becomes empty.
49
49
 
50
50
  * `on_shutdown(client)` **MAY** be called during the server's graceful shutdown process, _before_ the connection is closed and in addition to the `on_close` function (which is called _after_ the connection is closed.
51
51
 
52
- * `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `close` being called, etc').
52
+ * `on_close(client)` **MUST** be called _after_ the connection was closed for whatever reason (socket errors, parsing errors, timeouts, client disconnection, `client.close` being called, etc').
53
53
 
54
54
 
55
55
  The server **MUST** provide the Callback Object with a `client` object, that supports the following methods (this approach promises applications could be server agnostic):
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a WebSocket / SSE notification example application.
4
+ #
5
+ # In this example, WebSocket sub-protocols are explored.
6
+ #
7
+ # Running this application from the command line is easy with:
8
+ #
9
+ # iodine
10
+ #
11
+ # Or, in a single thread and a single process:
12
+ #
13
+ # iodine -t 1 -w 1
14
+ #
15
+ # Test using:
16
+ #
17
+ # var subprotocol = "echo"; // or "chat"
18
+ # ws = new WebSocket("ws://localhost:3000/Mitchel", subprotocol);
19
+ # ws.onmessage = function(e) { console.log(e.data); };
20
+ # ws.onclose = function(e) { console.log("Closed"); };
21
+ # ws.onopen = function(e) { e.target.send("Yo!"); };
22
+
23
+
24
+ # Chat clients connect with the "chat" sub-protocol.
25
+ class ChatClient
26
+ def on_open client
27
+ @nickname = client.env['PATH_INFO'].to_s.split('/')[1] || "Guest"
28
+ client.subscribe :chat
29
+ client.publish :chat , "#{@nickname} joined the chat."
30
+ end
31
+ def on_close client
32
+ client.publish :chat , "#{@nickname} left the chat."
33
+ end
34
+ def on_shutdown client
35
+ client.write "Server is shutting down... disconnecting all clients. Goodbye."
36
+ end
37
+ def on_message client, message
38
+ client.publish :chat , "#{@nickname}: #{message}"
39
+ end
40
+ end
41
+
42
+ # Echo clients connect with the "echo" sub-protocol.
43
+ class EchoClient
44
+ def on_open client
45
+ client.write "You established an echo connection."
46
+ end
47
+ def on_shutdown client
48
+ client.write "Server is shutting down... goodbye."
49
+ end
50
+ def on_message client, message
51
+ client.write message
52
+ end
53
+ end
54
+
55
+ # Rack application module
56
+ module APP
57
+ # the allowed protocols
58
+ CHAT_PROTOCOL_NAME = "chat"
59
+ ECHO_PROTOCOL_NAME = "echo"
60
+ PROTOCOLS =[CHAT_PROTOCOL_NAME, ECHO_PROTOCOL_NAME]
61
+
62
+ # the Rack application
63
+ def call env
64
+ return [200, {}, ["Hello World"]] unless env["rack.upgrade?"]
65
+ protocol = select_protocol(env)
66
+ case(protocol)
67
+ when CHAT_PROTOCOL_NAME
68
+ env["rack.upgrade"] = ChatClient.new
69
+ [101, { "Sec-Websocket-Protocol" => protocol }, []]
70
+ when ECHO_PROTOCOL_NAME
71
+ env["rack.upgrade"] = EchoClient.new
72
+ [101, { "Sec-Websocket-Protocol" => protocol }, []]
73
+ else
74
+ [400, {}, ["Unsupported protocol specified"]]
75
+ end
76
+ end
77
+
78
+ def select_protocol(env)
79
+ request_protocols = env["HTTP_SEC_WEBSOCKET_PROTOCOL"]
80
+ unless request_protocols.nil?
81
+ request_protocols = request_protocols.split(/,\s?/) if request_protocols.is_a?(String)
82
+ request_protocols.detect { |request_protocol| PROTOCOLS.include? request_protocol }
83
+ end # either `nil` or the result of `request_protocols.detect` are returned
84
+ end
85
+
86
+ # make functions availble as singleton module
87
+ extend self
88
+ end
89
+
90
+ run APP
data/exe/iodine CHANGED
@@ -33,7 +33,9 @@ module Iodine
33
33
  puts " Running only static file service."
34
34
  opt = ::Rack::Server::Options.new.parse!([])
35
35
  else
36
- puts cli_parser
36
+ puts "\nERROR: Couldn't run Ruby application, check command line arguments."
37
+ ARGV << "-?"
38
+ Iodine::Base::CLI.parse
37
39
  exit(0);
38
40
  end
39
41
  end
@@ -1456,26 +1456,35 @@ void fio_expected_concurrency(int16_t *threads, int16_t *processes) {
1456
1456
  /* Set any option that is less than 0 be equal to cores/value */
1457
1457
  /* Set any option equal to 0 be equal to the other option in value */
1458
1458
  ssize_t cpu_count = fio_detect_cpu_cores();
1459
- size_t cpu_adjust = (*processes <= 0 ? 1 : 0);
1459
+ size_t thread_cpu_adjust = (*threads <= 0 ? 1 : 0);
1460
+ size_t worker_cpu_adjust = (*processes <= 0 ? 1 : 0);
1460
1461
 
1461
1462
  if (cpu_count > 0) {
1462
- int16_t tmp_threads = 0;
1463
+ int16_t tmp = 0;
1463
1464
  if (*threads < 0)
1464
- tmp_threads = (int16_t)(cpu_count / (*threads * -1));
1465
- else if (*threads == 0)
1466
- tmp_threads = -1 * *processes;
1467
- else
1468
- tmp_threads = *threads;
1465
+ tmp = (int16_t)(cpu_count / (*threads * -1));
1466
+ else if (*threads == 0) {
1467
+ tmp = -1 * *processes;
1468
+ thread_cpu_adjust = 0;
1469
+ } else
1470
+ tmp = *threads;
1469
1471
  if (*processes < 0)
1470
1472
  *processes = (int16_t)(cpu_count / (*processes * -1));
1471
- else if (*processes == 0)
1473
+ else if (*processes == 0) {
1472
1474
  *processes = -1 * *threads;
1473
- *threads = tmp_threads;
1474
- if (cpu_adjust && (*processes * tmp_threads) >= cpu_count &&
1475
+ worker_cpu_adjust = 0;
1476
+ }
1477
+ *threads = tmp;
1478
+ tmp = *processes;
1479
+ if (worker_cpu_adjust && (*processes * *threads) >= cpu_count &&
1475
1480
  cpu_count > 3) {
1476
- /* leave a core available for the kernel */
1481
+ /* leave a resources available for the kernel */
1477
1482
  --*processes;
1478
1483
  }
1484
+ if (thread_cpu_adjust && (*threads * tmp) >= cpu_count && cpu_count > 3) {
1485
+ /* leave a resources available for the kernel */
1486
+ --*threads;
1487
+ }
1479
1488
  }
1480
1489
  }
1481
1490
 
@@ -2334,11 +2343,12 @@ static intptr_t fio_unix_socket(const char *address, uint8_t server) {
2334
2343
  if (server) {
2335
2344
  unlink(addr.sun_path);
2336
2345
  if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
2346
+ // perror("couldn't bind unix socket");
2337
2347
  close(fd);
2338
2348
  return -1;
2339
2349
  }
2340
2350
  if (listen(fd, SOMAXCONN) < 0) {
2341
- // perror("couldn't start listening");
2351
+ // perror("couldn't start listening to unix socket");
2342
2352
  close(fd);
2343
2353
  return -1;
2344
2354
  }
@@ -4923,7 +4933,7 @@ void fio_unsubscribe(subscription_s *s) {
4923
4933
  /* test again within lock */
4924
4934
  if (fio_ls_embd_is_empty(&ch->subscriptions)) {
4925
4935
  fio_ch_set_remove(&c->channels, hashed, ch, NULL);
4926
- removed = 1;
4936
+ removed = (c != &fio_postoffice.filters);
4927
4937
  }
4928
4938
  fio_unlock(&c->lock);
4929
4939
  }
@@ -5220,7 +5230,7 @@ static void fio_publish2process(fio_msg_internal_s *m) {
5220
5230
  if (m->filter) {
5221
5231
  ch = fio_filter_find_dup(m->filter);
5222
5232
  if (!ch) {
5223
- return;
5233
+ goto finish;
5224
5234
  }
5225
5235
  } else {
5226
5236
  ch = fio_channel_find_dup(m->channel);
@@ -5248,6 +5258,7 @@ static void fio_publish2process(fio_msg_internal_s *m) {
5248
5258
  }
5249
5259
  fio_unlock(&fio_postoffice.patterns.lock);
5250
5260
  }
5261
+ finish:
5251
5262
  fio_msg_internal_free(m);
5252
5263
  }
5253
5264
 
@@ -5268,6 +5279,8 @@ static void fio_publish2process(fio_msg_internal_s *m) {
5268
5279
  #define FIO_SET_OBJ_DESTROY(obj) fio_unsubscribe(obj)
5269
5280
  #include <fio.h>
5270
5281
 
5282
+ #define FIO_CLUSTER_NAME_LIMIT 255
5283
+
5271
5284
  typedef struct cluster_pr_s {
5272
5285
  fio_protocol_s protocol;
5273
5286
  fio_msg_internal_s *msg;
@@ -5289,7 +5302,7 @@ static struct cluster_data_s {
5289
5302
  intptr_t uuid;
5290
5303
  fio_ls_s clients;
5291
5304
  fio_lock_i lock;
5292
- char name[128];
5305
+ char name[FIO_CLUSTER_NAME_LIMIT + 1];
5293
5306
  } cluster_data = {.clients = FIO_LS_INIT(cluster_data.clients),
5294
5307
  .lock = FIO_LOCK_INIT};
5295
5308
 
@@ -5306,10 +5319,9 @@ static void fio_cluster_data_cleanup(int delete_file) {
5306
5319
  fio_close(uuid);
5307
5320
  }
5308
5321
  }
5309
- cluster_data = (struct cluster_data_s){
5310
- .lock = FIO_LOCK_INIT,
5311
- .clients = (fio_ls_s)FIO_LS_INIT(cluster_data.clients),
5312
- };
5322
+ cluster_data.uuid = 0;
5323
+ cluster_data.lock = FIO_LOCK_INIT;
5324
+ cluster_data.clients = (fio_ls_s)FIO_LS_INIT(cluster_data.clients);
5313
5325
  }
5314
5326
 
5315
5327
  static void fio_cluster_cleanup(void *ignore) {
@@ -5323,7 +5335,8 @@ static void fio_cluster_init(void) {
5323
5335
  /* create a unique socket name */
5324
5336
  char *tmp_folder = getenv("TMPDIR");
5325
5337
  uint32_t tmp_folder_len = 0;
5326
- if (!tmp_folder || ((tmp_folder_len = (uint32_t)strlen(tmp_folder)) > 100)) {
5338
+ if (!tmp_folder || ((tmp_folder_len = (uint32_t)strlen(tmp_folder)) >
5339
+ (FIO_CLUSTER_NAME_LIMIT - 28))) {
5327
5340
  #ifdef P_tmpdir
5328
5341
  tmp_folder = (char *)P_tmpdir;
5329
5342
  if (tmp_folder)
@@ -5333,7 +5346,7 @@ static void fio_cluster_init(void) {
5333
5346
  tmp_folder_len = 5;
5334
5347
  #endif
5335
5348
  }
5336
- if (tmp_folder_len >= 100) {
5349
+ if (tmp_folder_len >= (FIO_CLUSTER_NAME_LIMIT - 28)) {
5337
5350
  tmp_folder_len = 0;
5338
5351
  }
5339
5352
  if (tmp_folder_len) {
@@ -5343,8 +5356,9 @@ static void fio_cluster_init(void) {
5343
5356
  }
5344
5357
  memcpy(cluster_data.name + tmp_folder_len, "facil-io-sock-", 14);
5345
5358
  tmp_folder_len += 14;
5346
- tmp_folder_len += snprintf(cluster_data.name + tmp_folder_len,
5347
- 127 - tmp_folder_len, "%d", getpid());
5359
+ tmp_folder_len +=
5360
+ snprintf(cluster_data.name + tmp_folder_len,
5361
+ FIO_CLUSTER_NAME_LIMIT - tmp_folder_len, "%d", getpid());
5348
5362
  cluster_data.name[tmp_folder_len] = 0;
5349
5363
 
5350
5364
  /* remove if existing */
@@ -5505,6 +5519,7 @@ static void fio_cluster_on_close(intptr_t uuid, fio_protocol_s *pr_) {
5505
5519
  if (c->msg)
5506
5520
  fio_msg_internal_free(c->msg);
5507
5521
  c->msg = NULL;
5522
+ fio_sub_hash_free(&c->pubsub);
5508
5523
  fio_cluster_protocol_free(c);
5509
5524
  (void)uuid;
5510
5525
  }
@@ -5659,7 +5674,6 @@ static void fio_cluster_listen_on_close(intptr_t uuid,
5659
5674
  static void fio_listen2cluster(void *ignore) {
5660
5675
  /* this is called for each `fork`, but we only need this to run once. */
5661
5676
  fio_lock(&cluster_data.lock);
5662
- fio_cluster_init();
5663
5677
  cluster_data.uuid = fio_socket(cluster_data.name, NULL, 1);
5664
5678
  fio_unlock(&cluster_data.lock);
5665
5679
  if (cluster_data.uuid < 0) {
@@ -6671,8 +6685,10 @@ Allocator Initialization (initialize arenas and allocate a block for each CPU)
6671
6685
  #if DEBUG
6672
6686
  void fio_memory_dump_missing(void) {
6673
6687
  fprintf(stderr, "\n ==== Attempting Memory Dump (will crash) ====\n");
6674
- if (fio_ls_embd_any(&memory.available))
6688
+ if (fio_ls_embd_is_empty(&memory.available)) {
6689
+ fprintf(stderr, "- Memory dump attempt canceled\n");
6675
6690
  return;
6691
+ }
6676
6692
  block_node_s *smallest =
6677
6693
  FIO_LS_EMBD_OBJ(block_node_s, node, memory.available.next);
6678
6694
  FIO_LS_EMBD_FOR(&memory.available, node) {
@@ -6732,8 +6748,11 @@ static void fio_mem_destroy(void) {
6732
6748
  FIO_MEMORY_PRINT_BLOCK_STAT_END();
6733
6749
  size_t count = 0;
6734
6750
  FIO_LS_EMBD_FOR(&memory.available, node) { ++count; }
6735
- FIO_LOG_DEBUG("Memory pool size: %zu (%zu blocks per allocation).", count,
6736
- (size_t)FIO_MEMORY_BLOCKS_PER_ALLOCATION);
6751
+ FIO_LOG_DEBUG("Memory blocks in pool: %zu (%zu blocks per allocation).",
6752
+ count, (size_t)FIO_MEMORY_BLOCKS_PER_ALLOCATION);
6753
+ #if FIO_MEM_DUMP
6754
+ fio_memory_dump_missing();
6755
+ #endif
6737
6756
  }
6738
6757
  big_free(arenas);
6739
6758
  arenas = NULL;
@@ -8545,7 +8564,7 @@ Testing Memory Allocator
8545
8564
  #define fio_malloc_test() \
8546
8565
  fprintf(stderr, "\n=== SKIPPED facil.io memory allocator (bypassed)\n");
8547
8566
  #else
8548
- void fio_malloc_test(void) {
8567
+ FIO_FUNC void fio_malloc_test(void) {
8549
8568
  fprintf(stderr, "\n=== Testing facil.io memory allocator's system calls\n");
8550
8569
  char *mem = sys_alloc(FIO_MEMORY_BLOCK_SIZE, 0);
8551
8570
  FIO_ASSERT(mem, "sys_alloc failed to allocate memory!\n");
@@ -8558,7 +8577,7 @@ void fio_malloc_test(void) {
8558
8577
  sys_realloc(mem, FIO_MEMORY_BLOCK_SIZE, FIO_MEMORY_BLOCK_SIZE * 2);
8559
8578
  if (mem == mem2)
8560
8579
  fprintf(stderr, "* Performed system realloc without copy :-)\n");
8561
- FIO_ASSERT(mem2[0] = 'a' && mem2[FIO_MEMORY_BLOCK_SIZE - 1] == 'z',
8580
+ FIO_ASSERT(mem2[0] == 'a' && mem2[FIO_MEMORY_BLOCK_SIZE - 1] == 'z',
8562
8581
  "Reaclloc data was lost!");
8563
8582
  sys_free(mem2, FIO_MEMORY_BLOCK_SIZE * 2);
8564
8583
  fprintf(stderr, "=== Testing facil.io memory allocator's internal data.\n");
@@ -8572,8 +8591,10 @@ void fio_malloc_test(void) {
8572
8591
  mem[0] = 'a';
8573
8592
  FIO_ASSERT(mem[0] == 'a', "allocate memory wasn't written to!\n");
8574
8593
  mem = fio_realloc(mem, 1);
8594
+ FIO_ASSERT(mem, "fio_realloc failed!\n");
8575
8595
  FIO_ASSERT(mem[0] == 'a', "fio_realloc memory wasn't copied!\n");
8576
8596
  FIO_ASSERT(arena_last_used, "arena_last_used wasn't initialized!\n");
8597
+ fio_free(mem);
8577
8598
  block_s *b = arena_last_used->block;
8578
8599
 
8579
8600
  /* move arena to block's start */
@@ -8639,7 +8660,9 @@ void fio_malloc_test(void) {
8639
8660
  ++count;
8640
8661
  } while (arena_last_used->block == b);
8641
8662
 
8663
+ mem2 = mem;
8642
8664
  mem = fio_calloc(FIO_MEMORY_BLOCK_ALLOC_LIMIT - 64, 1);
8665
+ fio_free(mem2);
8643
8666
  FIO_ASSERT(mem,
8644
8667
  "failed to allocate FIO_MEMORY_BLOCK_ALLOC_LIMIT - 64 bytes!\n");
8645
8668
  FIO_ASSERT(((uintptr_t)mem & FIO_MEMORY_BLOCK_MASK) != 16,
@@ -8669,11 +8692,18 @@ void fio_malloc_test(void) {
8669
8692
  void *m0 = fio_malloc(0);
8670
8693
  void *rm0 = fio_realloc(m0, 16);
8671
8694
  FIO_ASSERT(m0 != rm0, "fio_realloc(fio_malloc(0), 16) failed!\n");
8695
+ fio_free(rm0);
8672
8696
  }
8673
8697
  {
8698
+ size_t pool_size = 0;
8699
+ FIO_LS_EMBD_FOR(&memory.available, node) { ++pool_size; }
8674
8700
  mem = fio_mmap(512);
8675
8701
  FIO_ASSERT(mem, "fio_mmap allocation failed!\n");
8676
8702
  fio_free(mem);
8703
+ size_t new_pool_size = 0;
8704
+ FIO_LS_EMBD_FOR(&memory.available, node) { ++new_pool_size; }
8705
+ FIO_ASSERT(new_pool_size == pool_size,
8706
+ "fio_free of fio_mmap went to memory pool!\n");
8677
8707
  }
8678
8708
 
8679
8709
  fprintf(stderr, "* passed.\n");
@@ -8684,12 +8714,12 @@ void fio_malloc_test(void) {
8684
8714
  Testing Core Callback add / remove / ensure
8685
8715
  ***************************************************************************** */
8686
8716
 
8687
- static void fio_state_callback_test_task(void *pi) {
8717
+ FIO_FUNC void fio_state_callback_test_task(void *pi) {
8688
8718
  ((uintptr_t *)pi)[0] += 1;
8689
8719
  }
8690
8720
 
8691
8721
  #define FIO_STATE_TEST_COUNT 10
8692
- static void fio_state_callback_order_test_task(void *pi) {
8722
+ FIO_FUNC void fio_state_callback_order_test_task(void *pi) {
8693
8723
  static uintptr_t start = FIO_STATE_TEST_COUNT;
8694
8724
  --start;
8695
8725
  FIO_ASSERT((uintptr_t)pi == start,
@@ -8697,7 +8727,7 @@ static void fio_state_callback_order_test_task(void *pi) {
8697
8727
  (size_t)pi);
8698
8728
  }
8699
8729
 
8700
- static void fio_state_callback_test(void) {
8730
+ FIO_FUNC void fio_state_callback_test(void) {
8701
8731
  fprintf(stderr, "=== Testing facil.io workflow state callback system\n");
8702
8732
  uintptr_t result = 0;
8703
8733
  uintptr_t other = 0;
@@ -8730,9 +8760,9 @@ static void fio_state_callback_test(void) {
8730
8760
  Testing fio_timers
8731
8761
  ***************************************************************************** */
8732
8762
 
8733
- static void fio_timer_test_task(void *arg) { ++(((size_t *)arg)[0]); }
8763
+ FIO_FUNC void fio_timer_test_task(void *arg) { ++(((size_t *)arg)[0]); }
8734
8764
 
8735
- static void fio_timer_test(void) {
8765
+ FIO_FUNC void fio_timer_test(void) {
8736
8766
  fprintf(stderr, "=== Testing facil.io timer system\n");
8737
8767
  size_t result = 0;
8738
8768
  const size_t total = 5;
@@ -8796,7 +8826,7 @@ static void fio_timer_test(void) {
8796
8826
  Testing listening socket
8797
8827
  ***************************************************************************** */
8798
8828
 
8799
- static void fio_socket_test(void) {
8829
+ FIO_FUNC void fio_socket_test(void) {
8800
8830
  /* initialize unix socket name */
8801
8831
  fio_str_s sock_name = FIO_STR_INIT;
8802
8832
  #ifdef P_tmpdir
@@ -8896,17 +8926,17 @@ static void fio_socket_test(void) {
8896
8926
  Testing listening socket
8897
8927
  ***************************************************************************** */
8898
8928
 
8899
- static void fio_cycle_test_task(void *arg) {
8929
+ FIO_FUNC void fio_cycle_test_task(void *arg) {
8900
8930
  fio_stop();
8901
8931
  (void)arg;
8902
8932
  }
8903
- static void fio_cycle_test_task2(void *arg) {
8933
+ FIO_FUNC void fio_cycle_test_task2(void *arg) {
8904
8934
  fprintf(stderr, "* facil.io cycling test fatal error!\n");
8905
8935
  exit(-1);
8906
8936
  (void)arg;
8907
8937
  }
8908
8938
 
8909
- static void fio_cycle_test(void) {
8939
+ FIO_FUNC void fio_cycle_test(void) {
8910
8940
  fprintf(stderr,
8911
8941
  "=== Testing facil.io cycling logic (partial - only tests timers)\n");
8912
8942
  fio_mark_time();
@@ -8931,18 +8961,18 @@ Testing fio_defer task system
8931
8961
  #define FIO_DEFER_TEST_PRINT 0
8932
8962
  #endif
8933
8963
 
8934
- static void sample_task(void *i_count, void *unused2) {
8964
+ FIO_FUNC void sample_task(void *i_count, void *unused2) {
8935
8965
  (void)(unused2);
8936
8966
  fio_atomic_add((uintptr_t *)i_count, 1);
8937
8967
  }
8938
8968
 
8939
- static void sched_sample_task(void *count, void *i_count) {
8969
+ FIO_FUNC void sched_sample_task(void *count, void *i_count) {
8940
8970
  for (size_t i = 0; i < (uintptr_t)count; i++) {
8941
8971
  fio_defer(sample_task, i_count, NULL);
8942
8972
  }
8943
8973
  }
8944
8974
 
8945
- static void fio_defer_test(void) {
8975
+ FIO_FUNC void fio_defer_test(void) {
8946
8976
  const size_t cpu_cores = fio_detect_cpu_cores();
8947
8977
  FIO_ASSERT(cpu_cores, "couldn't detect CPU cores!");
8948
8978
  uintptr_t i_count;
@@ -9015,8 +9045,8 @@ typedef struct {
9015
9045
  #define FIO_ARY_TYPE uintptr_t
9016
9046
  #include "fio.h"
9017
9047
 
9018
- static intptr_t ary_alloc_counter = 0;
9019
- static void copy_s(fio_ary_test_type_s *d, fio_ary_test_type_s *s) {
9048
+ FIO_FUNC intptr_t ary_alloc_counter = 0;
9049
+ FIO_FUNC void copy_s(fio_ary_test_type_s *d, fio_ary_test_type_s *s) {
9020
9050
  ++ary_alloc_counter;
9021
9051
  *d = *s;
9022
9052
  }
@@ -9028,7 +9058,7 @@ static void copy_s(fio_ary_test_type_s *d, fio_ary_test_type_s *s) {
9028
9058
  #define FIO_ARY_DESTROY(obj) (--ary_alloc_counter)
9029
9059
  #include "fio.h"
9030
9060
 
9031
- void fio_ary_test(void) {
9061
+ FIO_FUNC void fio_ary_test(void) {
9032
9062
  /* code */
9033
9063
  fio_i_ary__test();
9034
9064
  fio_s_ary__test();
@@ -9233,7 +9263,7 @@ FIO_FUNC void fio_set_test(void) {
9233
9263
  SipHash tests
9234
9264
  ***************************************************************************** */
9235
9265
 
9236
- static void fio_siphash_speed_test(void) {
9266
+ FIO_FUNC void fio_siphash_speed_test(void) {
9237
9267
  /* test based on code from BearSSL with credit to Thomas Pornin */
9238
9268
  uint8_t buffer[8192];
9239
9269
  memset(buffer, 'T', sizeof(buffer));
@@ -9283,7 +9313,7 @@ static void fio_siphash_speed_test(void) {
9283
9313
  }
9284
9314
  }
9285
9315
 
9286
- void fio_siphash_test(void) {
9316
+ FIO_FUNC void fio_siphash_test(void) {
9287
9317
  fprintf(stderr, "===================================\n");
9288
9318
  #if NODEBUG
9289
9319
  fio_siphash_speed_test();
@@ -9297,7 +9327,7 @@ void fio_siphash_test(void) {
9297
9327
  SHA-1 tests
9298
9328
  ***************************************************************************** */
9299
9329
 
9300
- static void fio_sha1_speed_test(void) {
9330
+ FIO_FUNC void fio_sha1_speed_test(void) {
9301
9331
  /* test based on code from BearSSL with credit to Thomas Pornin */
9302
9332
  uint8_t buffer[8192];
9303
9333
  uint8_t result[21];
@@ -9331,7 +9361,7 @@ static void fio_sha1_speed_test(void) {
9331
9361
  }
9332
9362
 
9333
9363
  #ifdef HAVE_OPENSSL
9334
- static void fio_sha1_open_ssl_speed_test(void) {
9364
+ FIO_FUNC void fio_sha1_open_ssl_speed_test(void) {
9335
9365
  /* test based on code from BearSSL with credit to Thomas Pornin */
9336
9366
  uint8_t buffer[8192];
9337
9367
  uint8_t result[21];
@@ -9365,7 +9395,7 @@ static void fio_sha1_open_ssl_speed_test(void) {
9365
9395
  }
9366
9396
  #endif
9367
9397
 
9368
- void fio_sha1_test(void) {
9398
+ FIO_FUNC void fio_sha1_test(void) {
9369
9399
  // clang-format off
9370
9400
  struct {
9371
9401
  char *str;
@@ -9436,12 +9466,13 @@ void fio_sha1_test(void) {
9436
9466
  SHA-2 tests
9437
9467
  ***************************************************************************** */
9438
9468
 
9439
- static char *sha2_variant_names[] = {
9469
+ FIO_FUNC char *sha2_variant_names[] = {
9440
9470
  "unknown", "SHA_512", "SHA_256", "SHA_512_256",
9441
9471
  "SHA_224", "SHA_512_224", "none", "SHA_384",
9442
9472
  };
9443
9473
 
9444
- static void fio_sha2_speed_test(fio_sha2_variant_e var, const char *var_name) {
9474
+ FIO_FUNC void fio_sha2_speed_test(fio_sha2_variant_e var,
9475
+ const char *var_name) {
9445
9476
  /* test based on code from BearSSL with credit to Thomas Pornin */
9446
9477
  uint8_t buffer[8192];
9447
9478
  uint8_t result[65];
@@ -9474,9 +9505,9 @@ static void fio_sha2_speed_test(fio_sha2_variant_e var, const char *var_name) {
9474
9505
  }
9475
9506
  }
9476
9507
 
9477
- static void fio_sha2_openssl_speed_test(const char *var_name, int (*init)(),
9478
- int (*update)(), int (*final)(),
9479
- void *sha) {
9508
+ FIO_FUNC void fio_sha2_openssl_speed_test(const char *var_name, int (*init)(),
9509
+ int (*update)(), int (*final)(),
9510
+ void *sha) {
9480
9511
  /* test adapted from BearSSL code with credit to Thomas Pornin */
9481
9512
  uint8_t buffer[8192];
9482
9513
  uint8_t result[1024];
@@ -9507,7 +9538,7 @@ static void fio_sha2_openssl_speed_test(const char *var_name, int (*init)(),
9507
9538
  cycles <<= 1;
9508
9539
  }
9509
9540
  }
9510
- void fio_sha2_test(void) {
9541
+ FIO_FUNC void fio_sha2_test(void) {
9511
9542
  fio_sha2_s s;
9512
9543
  char *expect;
9513
9544
  char *got;
@@ -9651,7 +9682,7 @@ error:
9651
9682
  Base64 tests
9652
9683
  ***************************************************************************** */
9653
9684
 
9654
- static void fio_base64_speed_test(void) {
9685
+ FIO_FUNC void fio_base64_speed_test(void) {
9655
9686
  /* test based on code from BearSSL with credit to Thomas Pornin */
9656
9687
  char buffer[8192];
9657
9688
  char result[8192 * 2];
@@ -9706,7 +9737,7 @@ static void fio_base64_speed_test(void) {
9706
9737
  }
9707
9738
  }
9708
9739
 
9709
- void fio_base64_test(void) {
9740
+ FIO_FUNC void fio_base64_test(void) {
9710
9741
  struct {
9711
9742
  char *str;
9712
9743
  char *base64;
@@ -9780,7 +9811,7 @@ void fio_base64_test(void) {
9780
9811
  Random Testing
9781
9812
  ***************************************************************************** */
9782
9813
 
9783
- void fio_test_random(void) {
9814
+ FIO_FUNC void fio_test_random(void) {
9784
9815
  fprintf(stderr, "=== Testing random generator\n");
9785
9816
  uint64_t rnd = fio_rand64();
9786
9817
  FIO_ASSERT((rnd != fio_rand64() && rnd != fio_rand64()),
@@ -9818,7 +9849,7 @@ void fio_test_random(void) {
9818
9849
  Poll (not kqueue or epoll) tests
9819
9850
  ***************************************************************************** */
9820
9851
  #if FIO_ENGINE_POLL
9821
- static void fio_poll_test(void) {
9852
+ FIO_FUNC void fio_poll_test(void) {
9822
9853
  fprintf(stderr, "=== Testing poll add / remove fd\n");
9823
9854
  fio_poll_add(5);
9824
9855
  FIO_ASSERT(fio_data->start == 5,
@@ -9869,11 +9900,11 @@ static void fio_poll_test(void) {
9869
9900
  Test UUID Linking
9870
9901
  ***************************************************************************** */
9871
9902
 
9872
- static void fio_uuid_link_test_on_close(void *obj) {
9903
+ FIO_FUNC void fio_uuid_link_test_on_close(void *obj) {
9873
9904
  fio_atomic_add((uintptr_t *)obj, 1);
9874
9905
  }
9875
9906
 
9876
- static void fio_uuid_link_test(void) {
9907
+ FIO_FUNC void fio_uuid_link_test(void) {
9877
9908
  fprintf(stderr, "=== Testing fio_uuid_link\n");
9878
9909
  uintptr_t called = 0;
9879
9910
  uintptr_t removed = 0;
@@ -9896,7 +9927,7 @@ static void fio_uuid_link_test(void) {
9896
9927
  Byte Order Testing
9897
9928
  ***************************************************************************** */
9898
9929
 
9899
- static void fio_str2u_test(void) {
9930
+ FIO_FUNC void fio_str2u_test(void) {
9900
9931
  fprintf(stderr, "=== Testing fio_u2strX and fio_u2strX functions.\n");
9901
9932
  char buffer[32];
9902
9933
  for (int64_t i = -1024; i < 1024; ++i) {
@@ -9929,15 +9960,15 @@ Pub/Sub partial tests
9929
9960
 
9930
9961
  #if FIO_PUBSUB_SUPPORT
9931
9962
 
9932
- static void fio_pubsub_test_on_message(fio_msg_s *msg) {
9963
+ FIO_FUNC void fio_pubsub_test_on_message(fio_msg_s *msg) {
9933
9964
  fio_atomic_add((uintptr_t *)msg->udata1, 1);
9934
9965
  }
9935
- static void fio_pubsub_test_on_unsubscribe(void *udata1, void *udata2) {
9966
+ FIO_FUNC void fio_pubsub_test_on_unsubscribe(void *udata1, void *udata2) {
9936
9967
  fio_atomic_add((uintptr_t *)udata1, 1);
9937
9968
  (void)udata2;
9938
9969
  }
9939
9970
 
9940
- static void fio_pubsub_test(void) {
9971
+ FIO_FUNC void fio_pubsub_test(void) {
9941
9972
  fprintf(stderr, "=== Testing pub/sub (partial)\n");
9942
9973
  fio_data->active = 1;
9943
9974
  fio_data->is_worker = 1;
@@ -9953,10 +9984,11 @@ static void fio_pubsub_test(void) {
9953
9984
  fio_u2str32((uint8_t *)buffer, 4);
9954
9985
  FIO_ASSERT(fio_str2u32((uint8_t *)buffer) == 4,
9955
9986
  "fio_u2str32 / fio_str2u32 not reversible (4)!");
9956
- s = fio_subscribe(.filter = 1, .udata1 = &counter,
9987
+ subscription_s *s2 =
9988
+ fio_subscribe(.filter = 1, .udata1 = &counter,
9957
9989
  .on_message = fio_pubsub_test_on_message,
9958
9990
  .on_unsubscribe = fio_pubsub_test_on_unsubscribe);
9959
- FIO_ASSERT(s, "fio_subscribe FAILED on filtered subscription.");
9991
+ FIO_ASSERT(s2, "fio_subscribe FAILED on filtered subscription.");
9960
9992
  fio_publish(.filter = 1);
9961
9993
  ++expect;
9962
9994
  fio_defer_perform();
@@ -9965,6 +9997,7 @@ static void fio_pubsub_test(void) {
9965
9997
  fio_defer_perform();
9966
9998
  FIO_ASSERT(counter == expect, "publishing to filter 2 arrived at filter 1!");
9967
9999
  fio_unsubscribe(s);
10000
+ fio_unsubscribe(s2);
9968
10001
  ++expect;
9969
10002
  fio_defer_perform();
9970
10003
  FIO_ASSERT(counter == expect, "unsubscribe wasn't called for filter 1!");
@@ -9987,6 +10020,7 @@ static void fio_pubsub_test(void) {
9987
10020
  fio_data->is_worker = 0;
9988
10021
  fio_data->active = 0;
9989
10022
  fio_data->workers = 0;
10023
+ fio_defer_perform();
9990
10024
  (void)fio_pubsub_test_on_message;
9991
10025
  (void)fio_pubsub_test_on_unsubscribe;
9992
10026
  fprintf(stderr, "* passed.\n");
@@ -10004,7 +10038,7 @@ String 2 Number and Number 2 String (partial) testing
10004
10038
  #else
10005
10039
  #define FIO_ATOL_TEST_MAX_CYCLES 4096
10006
10040
  #endif
10007
- static void fio_atol_test(void) {
10041
+ FIO_FUNC void fio_atol_test(void) {
10008
10042
  fprintf(stderr, "=== Testing fio_ltoa and fio_atol (partial)\n");
10009
10043
  #ifndef NODEBUG
10010
10044
  fprintf(stderr,
@@ -10159,7 +10193,7 @@ static void fio_atol_test(void) {
10159
10193
  String 2 Float and Float 2 String (partial) testing
10160
10194
  ***************************************************************************** */
10161
10195
 
10162
- static void fio_atof_test(void) {
10196
+ FIO_FUNC void fio_atof_test(void) {
10163
10197
  fprintf(stderr, "=== Testing fio_ftoa and fio_ftoa (partial)\n");
10164
10198
  #define TEST_DOUBLE(s, d, must) \
10165
10199
  do { \