├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── UPGRADING3.1.md ├── c_src ├── Makefile ├── erlzmq_nif.c ├── vector.c └── vector.h ├── doc └── overview.edoc ├── include └── erlzmq.hrl ├── perf ├── erlzmq_perf.erl ├── local_lat.erl ├── local_lat_active.erl ├── local_thr.erl ├── local_thr_active.erl ├── perfgraphs.py ├── remote_lat.erl ├── remote_lat_active.erl └── remote_thr.erl ├── rebar.config ├── src ├── erlzmq.app.src ├── erlzmq.erl └── erlzmq_nif.erl └── test └── erlzmq_test.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/*.app 2 | ebin/*.beam 3 | priv/*.so 4 | c_src/*.o 5 | deps 6 | graphs 7 | perf/*.beam 8 | .eunit 9 | doc/edoc-info 10 | doc/erlang.png 11 | doc/ezmq.html 12 | doc/index.html 13 | doc/overview-summary.html 14 | doc/packages-frame.html 15 | doc/stylesheet.css 16 | doc/modules-frame.html 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Yurii Rashkovskii, Evax Software and Michael Truog 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=rebar 2 | 3 | all: compile 4 | 5 | compile: 6 | @$(REBAR) compile 7 | 8 | perftest: compile 9 | @cd perf && erlc erlzmq_perf.erl 10 | 11 | clean: 12 | @$(REBAR) clean 13 | 14 | distclean: clean 15 | @cd c_src;make distclean 16 | 17 | test: compile 18 | @$(REBAR) eunit 19 | 20 | docs: 21 | @$(REBAR) doc 22 | 23 | bench: perftest 24 | @echo 'Running benchmarks, this could take some time...' 25 | @mkdir -p graphs 26 | @./perf/perfgraphs.py 27 | @mv -f *.png graphs/ 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | erlzmq2 2 | ======= 3 | NIF based Erlang bindings for the ZeroMQ messaging library. 4 | 5 | Copyright (c) 2011 Yurii Rashkovskii, Evax Software and Michael Truog 6 | 7 | Overview 8 | -------- 9 | 10 | The erlzmq2 application provides high-performance NIF based Erlang 11 | bindings for the ZeroMQ messaging library. 12 | 13 | Downloading 14 | ----------- 15 | 16 | The erlzmq2 source code can be found on 17 | [GitHub](https://github.com/zeromq/erlzmq2) 18 | 19 | $ git clone http://github.com/zeromq/erlzmq2.git 20 | 21 | Building 22 | -------- 23 | 24 | Please note that to behave properly on your system ZeroMQ might 25 | require [some tuning](http://www.zeromq.org/docs:tuning-zeromq). 26 | 27 | Build the code 28 | 29 | $ make 30 | 31 | If you want to build against a specific version of ZeroMQ in the 3.1 32 | series (not `v3.1.0`), use this: 33 | 34 | $ ZEROMQ_VERSION=v make 35 | 36 | Be aware that this will almost assuredly not work correctly for any 37 | versions of zeromq that are not in the 3.1 series. 38 | 39 | Build the docs 40 | 41 | $ make docs 42 | 43 | Run the test suite 44 | 45 | $ make test 46 | 47 | Run the benchmarks (requires [python](http://www.python.org) and 48 | [matplotlib](http://matplotlib.sourceforge.net/)) 49 | 50 | $ make bench 51 | 52 | This will run performance tests and output png graphs in the graphs 53 | directory. 54 | 55 | Architecture 56 | ------------ 57 | 58 | The bindings use Erlang's 59 | [NIF (native implemented functions)](http://www.erlang.org/doc/man/erl_nif.html) 60 | interface to achieve the best performance. One extra OS thread and one 61 | pair of inproc sockets by context are used to simulate blocking recv 62 | calls without affecting the Erlang virtual machine's responsiveness. 63 | 64 | License 65 | ------- 66 | 67 | The project is released under the MIT license. 68 | 69 | -------------------------------------------------------------------------------- /UPGRADING3.1.md: -------------------------------------------------------------------------------- 1 | Upgrading From 2.1 to 3.1 2 | ========================= 3 | 4 | See the general upgrading guidelines [here](http://www.zeromq.org/docs:3-1-upgrade). 5 | 6 | Things to watchout for in erlzmq2 7 | --------------------------------- 8 | 9 | The 'timeout' flag has been removed from the send receive flags. In 10 | 3.1 the native zeromq `sndtimeo` and `rcvtimeo` flags where added. You 11 | should use these instead. 12 | 13 | Also, is in zeromq as a whole the hwm flag has been replaced with 14 | sndhwm and rcvhwm. 15 | 16 | Things not to worry about 17 | ------------------------- 18 | 19 | The rest of the things in that upgrade guide can be ignored. The 20 | erlzmq2 nif binding abstracts that away for you. 21 | 22 | -------------------------------------------------------------------------------- /c_src/Makefile: -------------------------------------------------------------------------------- 1 | LINUX=$(shell uname | grep Linux | wc -l | xargs echo) 2 | DEPS=../deps 3 | 4 | ifeq ($(LINUX),1) 5 | ZMQ_FLAGS=--with-pic 6 | else 7 | ZMQ_FLAGS= 8 | endif 9 | 10 | ifndef ZEROMQ_VERSION 11 | ZEROMQ_VERSION=3.2.2 12 | endif 13 | 14 | all: $(DEPS)/zeromq3/src/.libs/libzmq.a 15 | 16 | clean: 17 | if test -e $(DEPS)/zeromq3/Makefile; then \ 18 | cd $(DEPS)/zeromq3; make clean; \ 19 | else \ 20 | true; \ 21 | fi 22 | 23 | distclean: 24 | @rm -rf $(DEPS) 25 | 26 | $(DEPS)/zeromq3: 27 | @mkdir -p $(DEPS) 28 | @curl -L https://archive.org/download/zeromq_$(ZEROMQ_VERSION)/zeromq-$(ZEROMQ_VERSION).tar.gz -o $(DEPS)/zeromq-$(ZEROMQ_VERSION).tar.gz 29 | @cd $(DEPS) && tar xzvfp zeromq-$(ZEROMQ_VERSION).tar.gz && mv zeromq-$(ZEROMQ_VERSION) zeromq3 30 | 31 | $(DEPS)/zeromq3/src/.libs/libzmq.a: $(DEPS)/zeromq3 32 | @cd $(DEPS)/zeromq3 && ./configure $(ZMQ_FLAGS) && make 33 | -------------------------------------------------------------------------------- /c_src/erlzmq_nif.c: -------------------------------------------------------------------------------- 1 | // -*- coding:utf-8;Mode:C;tab-width:2;c-basic-offset:2;indent-tabs-mode:nil -*- 2 | // ex: set softtabstop=2 tabstop=2 shiftwidth=2 expandtab fileencoding=utf-8: 3 | // 4 | // Copyright (c) 2011 Yurii Rashkovskii, Evax Software and Michael Truog 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | #include "zmq.h" 25 | #include "erl_nif.h" 26 | #include "vector.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define ERLZMQ_MAX_CONCURRENT_REQUESTS 16384 33 | 34 | static ErlNifResourceType* erlzmq_nif_resource_context; 35 | static ErlNifResourceType* erlzmq_nif_resource_socket; 36 | 37 | typedef struct erlzmq_context { 38 | void * context_zmq; 39 | void * thread_socket; 40 | char * thread_socket_name; 41 | int64_t socket_index; 42 | ErlNifTid polling_tid; 43 | ErlNifMutex * mutex; 44 | } erlzmq_context_t; 45 | 46 | #define ERLZMQ_SOCKET_ACTIVE_OFF 0 47 | #define ERLZMQ_SOCKET_ACTIVE_PENDING 1 48 | #define ERLZMQ_SOCKET_ACTIVE_ON 2 49 | 50 | typedef struct erlzmq_socket { 51 | erlzmq_context_t * context; 52 | int64_t socket_index; 53 | void * socket_zmq; 54 | int active; 55 | ErlNifPid active_pid; 56 | ErlNifMutex * mutex; 57 | } erlzmq_socket_t; 58 | 59 | #define ERLZMQ_THREAD_REQUEST_SEND 1 60 | #define ERLZMQ_THREAD_REQUEST_RECV 2 61 | #define ERLZMQ_THREAD_REQUEST_CLOSE 3 62 | #define ERLZMQ_THREAD_REQUEST_TERM 4 63 | 64 | typedef struct { 65 | int type; 66 | union { 67 | struct { 68 | erlzmq_socket_t * socket; 69 | ErlNifEnv * env; 70 | ERL_NIF_TERM ref; 71 | int flags; 72 | zmq_msg_t msg; 73 | ErlNifPid pid; 74 | } send; 75 | struct { 76 | erlzmq_socket_t * socket; 77 | ErlNifEnv * env; 78 | ERL_NIF_TERM ref; 79 | int flags; 80 | ErlNifPid pid; 81 | } recv; 82 | struct { 83 | erlzmq_socket_t * socket; 84 | ErlNifEnv * env; 85 | ERL_NIF_TERM ref; 86 | ErlNifPid pid; 87 | } close; 88 | struct { 89 | ErlNifEnv * env; 90 | ERL_NIF_TERM ref; 91 | ErlNifPid pid; 92 | } term; 93 | } data; 94 | } erlzmq_thread_request_t; 95 | 96 | // Prototypes 97 | #define NIF(name) \ 98 | ERL_NIF_TERM name(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 99 | 100 | NIF(erlzmq_nif_context); 101 | NIF(erlzmq_nif_socket); 102 | NIF(erlzmq_nif_bind); 103 | NIF(erlzmq_nif_connect); 104 | NIF(erlzmq_nif_setsockopt); 105 | NIF(erlzmq_nif_getsockopt); 106 | NIF(erlzmq_nif_send); 107 | NIF(erlzmq_nif_recv); 108 | NIF(erlzmq_nif_close); 109 | NIF(erlzmq_nif_term); 110 | NIF(erlzmq_nif_version); 111 | 112 | static void * polling_thread(void * handle); 113 | static ERL_NIF_TERM add_active_req(ErlNifEnv* env, erlzmq_socket_t * socket); 114 | static ERL_NIF_TERM return_zmq_errno(ErlNifEnv* env, int const value); 115 | 116 | static ErlNifFunc nif_funcs[] = 117 | { 118 | {"context", 1, erlzmq_nif_context}, 119 | {"socket", 4, erlzmq_nif_socket}, 120 | {"bind", 2, erlzmq_nif_bind}, 121 | {"connect", 2, erlzmq_nif_connect}, 122 | {"setsockopt", 3, erlzmq_nif_setsockopt}, 123 | {"getsockopt", 2, erlzmq_nif_getsockopt}, 124 | {"send", 3, erlzmq_nif_send}, 125 | {"recv", 2, erlzmq_nif_recv}, 126 | {"close", 1, erlzmq_nif_close}, 127 | {"term", 1, erlzmq_nif_term}, 128 | {"version", 0, erlzmq_nif_version} 129 | }; 130 | 131 | NIF(erlzmq_nif_context) 132 | { 133 | int thread_count; 134 | 135 | if (! enif_get_int(env, argv[0], &thread_count)) { 136 | return enif_make_badarg(env); 137 | } 138 | 139 | erlzmq_context_t * context = enif_alloc_resource(erlzmq_nif_resource_context, 140 | sizeof(erlzmq_context_t)); 141 | assert(context); 142 | context->context_zmq = zmq_init(thread_count); 143 | if (! context->context_zmq) { 144 | enif_release_resource(context); 145 | return return_zmq_errno(env, zmq_errno()); 146 | } 147 | 148 | char thread_socket_id[64]; 149 | sprintf(thread_socket_id, "inproc://erlzmq-%ld", (long int) context); 150 | context->thread_socket = zmq_socket(context->context_zmq, ZMQ_PUSH); 151 | assert(context->thread_socket); 152 | context->mutex = enif_mutex_create("erlzmq_context_t_mutex"); 153 | assert(context->mutex); 154 | if (zmq_bind(context->thread_socket, thread_socket_id)) { 155 | zmq_close(context->thread_socket); 156 | enif_mutex_destroy(context->mutex); 157 | zmq_term(context->context_zmq); 158 | enif_release_resource(context); 159 | return return_zmq_errno(env, zmq_errno()); 160 | } 161 | context->thread_socket_name = strdup(thread_socket_id); 162 | assert(context->thread_socket_name); 163 | context->socket_index = 1; 164 | 165 | int const value_errno = enif_thread_create("erlzmq_polling_thread", 166 | &context->polling_tid, 167 | polling_thread, context, NULL); 168 | if (value_errno) { 169 | free(context->thread_socket_name); 170 | zmq_close(context->thread_socket); 171 | enif_mutex_destroy(context->mutex); 172 | zmq_term(context->context_zmq); 173 | enif_release_resource(context); 174 | return return_zmq_errno(env, value_errno); 175 | } 176 | 177 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 178 | enif_make_resource(env, context)); 179 | } 180 | 181 | NIF(erlzmq_nif_socket) 182 | { 183 | erlzmq_context_t * context; 184 | int socket_type; 185 | int active; 186 | ErlNifPid active_pid; 187 | 188 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_context, 189 | (void **) &context)) { 190 | return enif_make_badarg(env); 191 | } 192 | 193 | if (! enif_get_int(env, argv[1], &socket_type)) { 194 | return enif_make_badarg(env); 195 | } 196 | 197 | if (! enif_get_int(env, argv[2], &active)) { 198 | return enif_make_badarg(env); 199 | } 200 | 201 | if (! enif_get_local_pid(env, argv[3], &active_pid)) { 202 | return enif_make_badarg(env); 203 | } 204 | 205 | erlzmq_socket_t * socket = enif_alloc_resource(erlzmq_nif_resource_socket, 206 | sizeof(erlzmq_socket_t)); 207 | assert(socket); 208 | socket->context = context; 209 | socket->socket_index = context->socket_index; 210 | socket->socket_zmq = zmq_socket(context->context_zmq, socket_type); 211 | if (! socket->socket_zmq) { 212 | enif_release_resource(socket); 213 | return return_zmq_errno(env, zmq_errno()); 214 | } 215 | socket->active = active; 216 | socket->active_pid = active_pid; 217 | socket->mutex = enif_mutex_create("erlzmq_socket_t_mutex"); 218 | assert(socket->mutex); 219 | 220 | context->socket_index++; 221 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), enif_make_tuple2(env, 222 | enif_make_uint64(env, socket->socket_index), 223 | enif_make_resource(env, socket))); 224 | } 225 | 226 | NIF(erlzmq_nif_bind) 227 | { 228 | erlzmq_socket_t * socket; 229 | unsigned endpoint_length; 230 | 231 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 232 | (void **) &socket)) { 233 | return enif_make_badarg(env); 234 | } 235 | 236 | if (! enif_get_list_length(env, argv[1], &endpoint_length)) { 237 | return enif_make_badarg(env); 238 | } 239 | 240 | char * endpoint = (char *) malloc(endpoint_length + 1); 241 | if (! enif_get_string(env, argv[1], endpoint, endpoint_length + 1, 242 | ERL_NIF_LATIN1)) { 243 | return enif_make_badarg(env); 244 | } 245 | 246 | if (! socket->mutex) { 247 | free(endpoint); 248 | return return_zmq_errno(env, ETERM); 249 | } 250 | enif_mutex_lock(socket->mutex); 251 | if (! socket->socket_zmq) { 252 | if (socket->mutex) { 253 | enif_mutex_unlock(socket->mutex); 254 | } 255 | free(endpoint); 256 | return return_zmq_errno(env, ETERM); 257 | } 258 | else if (zmq_bind(socket->socket_zmq, endpoint)) { 259 | enif_mutex_unlock(socket->mutex); 260 | free(endpoint); 261 | return return_zmq_errno(env, zmq_errno()); 262 | } 263 | else if (socket->active == ERLZMQ_SOCKET_ACTIVE_PENDING) { 264 | socket->active = ERLZMQ_SOCKET_ACTIVE_ON; 265 | enif_mutex_unlock(socket->mutex); 266 | free(endpoint); 267 | return add_active_req(env, socket); 268 | } 269 | else { 270 | enif_mutex_unlock(socket->mutex); 271 | free(endpoint); 272 | return enif_make_atom(env, "ok"); 273 | } 274 | } 275 | 276 | NIF(erlzmq_nif_connect) 277 | { 278 | erlzmq_socket_t * socket; 279 | unsigned endpoint_length; 280 | 281 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 282 | (void **) &socket)) { 283 | return enif_make_badarg(env); 284 | } 285 | 286 | if (! enif_get_list_length(env, argv[1], &endpoint_length)) { 287 | return enif_make_badarg(env); 288 | } 289 | 290 | char * endpoint = (char *) malloc(endpoint_length + 1); 291 | if (! enif_get_string(env, argv[1], endpoint, endpoint_length + 1, 292 | ERL_NIF_LATIN1)) { 293 | return enif_make_badarg(env); 294 | } 295 | 296 | if (! socket->mutex) { 297 | return return_zmq_errno(env, ETERM); 298 | } 299 | enif_mutex_lock(socket->mutex); 300 | if (! socket->socket_zmq) { 301 | if (socket->mutex) { 302 | enif_mutex_unlock(socket->mutex); 303 | } 304 | return return_zmq_errno(env, ETERM); 305 | } 306 | else if (zmq_connect(socket->socket_zmq, endpoint)) { 307 | enif_mutex_unlock(socket->mutex); 308 | free(endpoint); 309 | return return_zmq_errno(env, zmq_errno()); 310 | } 311 | else if (socket->active == ERLZMQ_SOCKET_ACTIVE_PENDING) { 312 | socket->active = ERLZMQ_SOCKET_ACTIVE_ON; 313 | enif_mutex_unlock(socket->mutex); 314 | free(endpoint); 315 | return add_active_req(env, socket); 316 | } 317 | else { 318 | enif_mutex_unlock(socket->mutex); 319 | free(endpoint); 320 | return enif_make_atom(env, "ok"); 321 | } 322 | } 323 | 324 | NIF(erlzmq_nif_setsockopt) 325 | { 326 | erlzmq_socket_t * socket; 327 | int option_name; 328 | 329 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 330 | (void **) &socket)) { 331 | return enif_make_badarg(env); 332 | } 333 | 334 | if (! enif_get_int(env, argv[1], &option_name)) { 335 | return enif_make_badarg(env); 336 | } 337 | 338 | ErlNifUInt64 value_uint64; 339 | ErlNifSInt64 value_int64; 340 | ErlNifBinary value_binary; 341 | int value_int; 342 | void *option_value; 343 | size_t option_len; 344 | switch (option_name) { 345 | // uint64_t 346 | case ZMQ_AFFINITY: 347 | if (! enif_get_uint64(env, argv[2], &value_uint64)) { 348 | return enif_make_badarg(env); 349 | } 350 | option_value = &value_uint64; 351 | option_len = sizeof(int64_t); 352 | break; 353 | 354 | // int64_t 355 | case ZMQ_MAXMSGSIZE: 356 | if (! enif_get_int64(env, argv[2], &value_int64)) { 357 | return enif_make_badarg(env); 358 | } 359 | option_value = &value_int64; 360 | option_len = sizeof(int64_t); 361 | break; 362 | // binary 363 | case ZMQ_IDENTITY: 364 | case ZMQ_SUBSCRIBE: 365 | case ZMQ_UNSUBSCRIBE: 366 | if (! enif_inspect_iolist_as_binary(env, argv[2], &value_binary)) { 367 | return enif_make_badarg(env); 368 | } 369 | option_value = value_binary.data; 370 | option_len = value_binary.size; 371 | break; 372 | // int 373 | case ZMQ_SNDHWM: 374 | case ZMQ_RCVHWM: 375 | case ZMQ_RATE: 376 | case ZMQ_RECOVERY_IVL: 377 | case ZMQ_RCVBUF: 378 | case ZMQ_SNDBUF: 379 | case ZMQ_LINGER: 380 | case ZMQ_RECONNECT_IVL: 381 | case ZMQ_RECONNECT_IVL_MAX: 382 | case ZMQ_BACKLOG: 383 | case ZMQ_MULTICAST_HOPS: 384 | case ZMQ_RCVTIMEO: 385 | case ZMQ_SNDTIMEO: 386 | case ZMQ_IPV4ONLY: 387 | if (! enif_get_int(env, argv[2], &value_int)) { 388 | return enif_make_badarg(env); 389 | } 390 | option_value = &value_int; 391 | option_len = sizeof(int); 392 | break; 393 | default: 394 | return enif_make_badarg(env); 395 | } 396 | 397 | if (! socket->mutex) { 398 | return return_zmq_errno(env, ETERM); 399 | } 400 | enif_mutex_lock(socket->mutex); 401 | if (! socket->socket_zmq) { 402 | if (socket->mutex) { 403 | enif_mutex_unlock(socket->mutex); 404 | } 405 | return return_zmq_errno(env, ETERM); 406 | } 407 | else if (zmq_setsockopt(socket->socket_zmq, option_name, 408 | option_value, option_len)) { 409 | enif_mutex_unlock(socket->mutex); 410 | return return_zmq_errno(env, zmq_errno()); 411 | } 412 | else { 413 | enif_mutex_unlock(socket->mutex); 414 | return enif_make_atom(env, "ok"); 415 | } 416 | } 417 | 418 | NIF(erlzmq_nif_getsockopt) 419 | { 420 | erlzmq_socket_t * socket; 421 | int option_name; 422 | 423 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 424 | (void **) &socket)) { 425 | return enif_make_badarg(env); 426 | } 427 | 428 | if (! enif_get_int(env, argv[1], &option_name)) { 429 | return enif_make_badarg(env); 430 | } 431 | 432 | ErlNifBinary value_binary; 433 | int64_t value_int64; 434 | int64_t value_uint64; 435 | char option_value[256]; 436 | int value_int; 437 | size_t option_len; 438 | switch(option_name) { 439 | // int64_t 440 | case ZMQ_MAXMSGSIZE: 441 | option_len = sizeof(value_int64); 442 | if (! socket->mutex) { 443 | return return_zmq_errno(env, ETERM); 444 | } 445 | enif_mutex_lock(socket->mutex); 446 | if (! socket->socket_zmq) { 447 | if (socket->mutex) { 448 | enif_mutex_unlock(socket->mutex); 449 | } 450 | return return_zmq_errno(env, ETERM); 451 | } 452 | else if (zmq_getsockopt(socket->socket_zmq, option_name, 453 | &value_int64, &option_len)) { 454 | enif_mutex_unlock(socket->mutex); 455 | return return_zmq_errno(env, zmq_errno()); 456 | } 457 | enif_mutex_unlock(socket->mutex); 458 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 459 | enif_make_int64(env, value_int64)); 460 | // uint64_t 461 | case ZMQ_AFFINITY: 462 | option_len = sizeof(value_uint64); 463 | if (! socket->mutex) { 464 | return return_zmq_errno(env, ETERM); 465 | } 466 | enif_mutex_lock(socket->mutex); 467 | if (! socket->socket_zmq) { 468 | if (socket->mutex) { 469 | enif_mutex_unlock(socket->mutex); 470 | } 471 | return return_zmq_errno(env, ETERM); 472 | } 473 | else if (zmq_getsockopt(socket->socket_zmq, option_name, 474 | &value_uint64, &option_len)) { 475 | enif_mutex_unlock(socket->mutex); 476 | return return_zmq_errno(env, zmq_errno()); 477 | } 478 | enif_mutex_unlock(socket->mutex); 479 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 480 | enif_make_uint64(env, value_uint64)); 481 | // binary 482 | case ZMQ_IDENTITY: 483 | option_len = sizeof(option_value); 484 | if (! socket->mutex) { 485 | return return_zmq_errno(env, ETERM); 486 | } 487 | enif_mutex_lock(socket->mutex); 488 | if (! socket->socket_zmq) { 489 | if (socket->mutex) { 490 | enif_mutex_unlock(socket->mutex); 491 | } 492 | return return_zmq_errno(env, ETERM); 493 | } 494 | else if (zmq_getsockopt(socket->socket_zmq, option_name, 495 | option_value, &option_len)) { 496 | enif_mutex_unlock(socket->mutex); 497 | return return_zmq_errno(env, zmq_errno()); 498 | } 499 | enif_mutex_unlock(socket->mutex); 500 | enif_alloc_binary(option_len, &value_binary); 501 | memcpy(value_binary.data, option_value, option_len); 502 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 503 | enif_make_binary(env, &value_binary)); 504 | // int 505 | case ZMQ_TYPE: 506 | case ZMQ_RCVMORE: 507 | case ZMQ_SNDHWM: 508 | case ZMQ_RCVHWM: 509 | case ZMQ_RATE: 510 | case ZMQ_RECOVERY_IVL: 511 | case ZMQ_SNDBUF: 512 | case ZMQ_RCVBUF: 513 | case ZMQ_LINGER: 514 | case ZMQ_RECONNECT_IVL: 515 | case ZMQ_RECONNECT_IVL_MAX: 516 | case ZMQ_BACKLOG: 517 | case ZMQ_MULTICAST_HOPS: 518 | case ZMQ_RCVTIMEO: 519 | case ZMQ_SNDTIMEO: 520 | case ZMQ_IPV4ONLY: 521 | case ZMQ_EVENTS: 522 | case ZMQ_FD: // FIXME: ZMQ_FD returns SOCKET on Windows 523 | option_len = sizeof(value_int); 524 | if (! socket->mutex) { 525 | return return_zmq_errno(env, ETERM); 526 | } 527 | enif_mutex_lock(socket->mutex); 528 | if (! socket->socket_zmq) { 529 | if (socket->mutex) { 530 | enif_mutex_unlock(socket->mutex); 531 | } 532 | return return_zmq_errno(env, ETERM); 533 | } 534 | else if (zmq_getsockopt(socket->socket_zmq, option_name, 535 | &value_int, &option_len)) { 536 | enif_mutex_unlock(socket->mutex); 537 | return return_zmq_errno(env, zmq_errno()); 538 | } 539 | enif_mutex_unlock(socket->mutex); 540 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 541 | enif_make_int(env, value_int)); 542 | default: 543 | return enif_make_badarg(env); 544 | } 545 | } 546 | 547 | NIF(erlzmq_nif_send) 548 | { 549 | erlzmq_thread_request_t req; 550 | erlzmq_socket_t * socket; 551 | ErlNifBinary binary; 552 | 553 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 554 | (void **) &socket)) { 555 | return enif_make_badarg(env); 556 | } 557 | 558 | if (! enif_inspect_iolist_as_binary(env, argv[1], &binary)) { 559 | return enif_make_badarg(env); 560 | } 561 | 562 | if (! enif_get_int(env, argv[2], &req.data.send.flags)) { 563 | return enif_make_badarg(env); 564 | } 565 | 566 | if (zmq_msg_init_size(&req.data.send.msg, binary.size)) { 567 | return return_zmq_errno(env, zmq_errno()); 568 | } 569 | 570 | memcpy(zmq_msg_data(&req.data.send.msg), binary.data, binary.size); 571 | 572 | int polling_thread_send = 1; 573 | if (! socket->active) { 574 | // try send 575 | if (! socket->mutex) { 576 | return return_zmq_errno(env, ETERM); 577 | } 578 | enif_mutex_lock(socket->mutex); 579 | if (! socket->socket_zmq) { 580 | if (socket->mutex) { 581 | enif_mutex_unlock(socket->mutex); 582 | } 583 | return return_zmq_errno(env, ETERM); 584 | } 585 | else if (zmq_sendmsg(socket->socket_zmq, &req.data.send.msg, 586 | req.data.send.flags | ZMQ_DONTWAIT) == -1) { 587 | enif_mutex_unlock(socket->mutex); 588 | int const error = zmq_errno(); 589 | if (error != EAGAIN || 590 | (error == EAGAIN && (req.data.send.flags & ZMQ_DONTWAIT))) { 591 | zmq_msg_close(&req.data.send.msg); 592 | return return_zmq_errno(env, error); 593 | } 594 | // if it fails, use the context thread poll for the send 595 | } 596 | else { 597 | enif_mutex_unlock(socket->mutex); 598 | polling_thread_send = 0; 599 | } 600 | } 601 | 602 | if (polling_thread_send) { 603 | req.type = ERLZMQ_THREAD_REQUEST_SEND; 604 | req.data.send.env = enif_alloc_env(); 605 | req.data.send.ref = enif_make_ref(req.data.send.env); 606 | enif_self(env, &req.data.send.pid); 607 | req.data.send.socket = socket; 608 | 609 | zmq_msg_t msg; 610 | if (zmq_msg_init_size(&msg, sizeof(erlzmq_thread_request_t))) { 611 | zmq_msg_close(&req.data.send.msg); 612 | enif_free_env(req.data.send.env); 613 | return return_zmq_errno(env, zmq_errno()); 614 | } 615 | 616 | memcpy(zmq_msg_data(&msg), &req, sizeof(erlzmq_thread_request_t)); 617 | 618 | if (! socket->context->mutex) { 619 | return return_zmq_errno(env, ETERM); 620 | } 621 | enif_mutex_lock(socket->context->mutex); 622 | if (! socket->context->thread_socket_name) { 623 | enif_mutex_unlock(socket->context->mutex); 624 | return return_zmq_errno(env, ETERM); 625 | } 626 | else if (zmq_sendmsg(socket->context->thread_socket, &msg, 0) == -1) { 627 | enif_mutex_unlock(socket->context->mutex); 628 | 629 | zmq_msg_close(&msg); 630 | zmq_msg_close(&req.data.send.msg); 631 | enif_free_env(req.data.send.env); 632 | return return_zmq_errno(env, zmq_errno()); 633 | } 634 | else { 635 | enif_mutex_unlock(socket->context->mutex); 636 | 637 | zmq_msg_close(&msg); 638 | // each pointer to the socket in a request increments the reference 639 | enif_keep_resource(socket); 640 | 641 | return enif_make_copy(env, req.data.send.ref); 642 | } 643 | } 644 | else { 645 | zmq_msg_close(&req.data.send.msg); 646 | 647 | return enif_make_atom(env, "ok"); 648 | } 649 | } 650 | 651 | NIF(erlzmq_nif_recv) 652 | { 653 | erlzmq_thread_request_t req; 654 | erlzmq_socket_t * socket; 655 | 656 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 657 | (void **) &socket)) { 658 | return enif_make_badarg(env); 659 | } 660 | 661 | if (! enif_get_int(env, argv[1], &req.data.recv.flags)) { 662 | return enif_make_badarg(env); 663 | } 664 | 665 | if (socket->active) { 666 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 667 | enif_make_atom(env, "active")); 668 | } 669 | 670 | zmq_msg_t msg; 671 | if (zmq_msg_init(&msg)) { 672 | return return_zmq_errno(env, zmq_errno()); 673 | } 674 | // try recv with noblock 675 | // if it fails, use the context thread poll for the recv 676 | if (! socket->mutex) { 677 | zmq_msg_close(&msg); 678 | return return_zmq_errno(env, ETERM); 679 | } 680 | enif_mutex_lock(socket->mutex); 681 | if (! socket->socket_zmq) { 682 | if (socket->mutex) { 683 | enif_mutex_unlock(socket->mutex); 684 | } 685 | zmq_msg_close(&msg); 686 | return return_zmq_errno(env, ETERM); 687 | } 688 | else if (zmq_recvmsg(socket->socket_zmq, &msg, ZMQ_DONTWAIT) == -1) { 689 | enif_mutex_unlock(socket->mutex); 690 | int const error = zmq_errno(); 691 | zmq_msg_close(&msg); 692 | 693 | if (error != EAGAIN || 694 | (error == EAGAIN && (req.data.recv.flags & ZMQ_DONTWAIT))) { 695 | return return_zmq_errno(env, error); 696 | } 697 | 698 | req.type = ERLZMQ_THREAD_REQUEST_RECV; 699 | req.data.recv.env = enif_alloc_env(); 700 | req.data.recv.ref = enif_make_ref(req.data.recv.env); 701 | enif_self(env, &req.data.recv.pid); 702 | req.data.recv.socket = socket; 703 | 704 | if (zmq_msg_init_size(&msg, sizeof(erlzmq_thread_request_t)) == -1) { 705 | enif_free_env(req.data.recv.env); 706 | return return_zmq_errno(env, zmq_errno()); 707 | } 708 | 709 | memcpy(zmq_msg_data(&msg), &req, sizeof(erlzmq_thread_request_t)); 710 | 711 | if (! socket->context->mutex) { 712 | zmq_msg_close(&msg); 713 | enif_free_env(req.data.recv.env); 714 | return return_zmq_errno(env, ETERM); 715 | } 716 | enif_mutex_lock(socket->context->mutex); 717 | if (! socket->context->thread_socket_name) { 718 | if (socket->context->mutex) { 719 | enif_mutex_unlock(socket->context->mutex); 720 | } 721 | zmq_msg_close(&msg); 722 | enif_free_env(req.data.recv.env); 723 | return return_zmq_errno(env, ETERM); 724 | } 725 | else if (zmq_sendmsg(socket->context->thread_socket, &msg, 0) == -1) { 726 | enif_mutex_unlock(socket->context->mutex); 727 | zmq_msg_close(&msg); 728 | enif_free_env(req.data.recv.env); 729 | return return_zmq_errno(env, zmq_errno()); 730 | } 731 | else { 732 | enif_mutex_unlock(socket->context->mutex); 733 | zmq_msg_close(&msg); 734 | 735 | // each pointer to the socket in a request increments the reference 736 | enif_keep_resource(socket); 737 | return enif_make_copy(env, req.data.recv.ref); 738 | } 739 | } 740 | else { 741 | enif_mutex_unlock(socket->mutex); 742 | 743 | ErlNifBinary binary; 744 | enif_alloc_binary(zmq_msg_size(&msg), &binary); 745 | memcpy(binary.data, zmq_msg_data(&msg), zmq_msg_size(&msg)); 746 | 747 | zmq_msg_close(&msg); 748 | 749 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), 750 | enif_make_binary(env, &binary)); 751 | } 752 | } 753 | 754 | NIF(erlzmq_nif_close) 755 | { 756 | erlzmq_socket_t * socket; 757 | 758 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_socket, 759 | (void **) &socket)) { 760 | return enif_make_badarg(env); 761 | } 762 | 763 | erlzmq_thread_request_t req; 764 | req.type = ERLZMQ_THREAD_REQUEST_CLOSE; 765 | req.data.close.env = enif_alloc_env(); 766 | req.data.close.ref = enif_make_ref(req.data.close.env); 767 | enif_self(env, &req.data.close.pid); 768 | req.data.close.socket = socket; 769 | 770 | zmq_msg_t msg; 771 | if (zmq_msg_init_size(&msg, sizeof(erlzmq_thread_request_t))) { 772 | enif_free_env(req.data.close.env); 773 | return return_zmq_errno(env, zmq_errno()); 774 | } 775 | 776 | memcpy(zmq_msg_data(&msg), &req, sizeof(erlzmq_thread_request_t)); 777 | 778 | if (! socket->context->mutex) { 779 | zmq_msg_close(&msg); 780 | return return_zmq_errno(env, ETERM); 781 | } 782 | enif_mutex_lock(socket->context->mutex); 783 | if (! socket->context->thread_socket_name) { 784 | // context is gone 785 | if (socket->context->mutex) { 786 | enif_mutex_unlock(socket->context->mutex); 787 | } 788 | zmq_msg_close(&msg); 789 | enif_free_env(req.data.close.env); 790 | 791 | if (! socket->mutex) { 792 | return return_zmq_errno(env, ETERM); 793 | } 794 | enif_mutex_lock(socket->mutex); 795 | if (! socket->socket_zmq) { 796 | enif_mutex_unlock(socket->mutex); 797 | return return_zmq_errno(env, ETERM); 798 | } 799 | zmq_close(socket->socket_zmq); 800 | socket->socket_zmq = 0; 801 | enif_mutex_unlock(socket->mutex); 802 | enif_mutex_destroy(socket->mutex); 803 | socket->mutex = 0; 804 | enif_release_resource(socket); 805 | return enif_make_atom(env, "ok"); 806 | } 807 | else if (zmq_sendmsg(socket->context->thread_socket, &msg, 0) == -1) { 808 | enif_mutex_unlock(socket->context->mutex); 809 | zmq_msg_close(&msg); 810 | enif_free_env(req.data.close.env); 811 | return return_zmq_errno(env, zmq_errno()); 812 | } 813 | else { 814 | enif_mutex_unlock(socket->context->mutex); 815 | zmq_msg_close(&msg); 816 | // each pointer to the socket in a request increments the reference 817 | enif_keep_resource(socket); 818 | return enif_make_copy(env, req.data.close.ref); 819 | } 820 | } 821 | 822 | NIF(erlzmq_nif_term) 823 | { 824 | erlzmq_context_t * context; 825 | 826 | if (! enif_get_resource(env, argv[0], erlzmq_nif_resource_context, 827 | (void **) &context)) { 828 | return enif_make_badarg(env); 829 | } 830 | 831 | erlzmq_thread_request_t req; 832 | req.type = ERLZMQ_THREAD_REQUEST_TERM; 833 | req.data.term.env = enif_alloc_env(); 834 | req.data.term.ref = enif_make_ref(req.data.term.env); 835 | enif_self(env, &req.data.term.pid); 836 | 837 | zmq_msg_t msg; 838 | if (zmq_msg_init_size(&msg, sizeof(erlzmq_thread_request_t))) { 839 | enif_free_env(req.data.term.env); 840 | return return_zmq_errno(env, zmq_errno()); 841 | } 842 | 843 | memcpy(zmq_msg_data(&msg), &req, sizeof(erlzmq_thread_request_t)); 844 | 845 | if (! context->mutex) { 846 | zmq_msg_close(&msg); 847 | enif_free_env(req.data.term.env); 848 | return return_zmq_errno(env, ETERM); 849 | } 850 | enif_mutex_lock(context->mutex); 851 | if (! context->thread_socket_name) { 852 | if (context->mutex) { 853 | enif_mutex_unlock(context->mutex); 854 | } 855 | zmq_msg_close(&msg); 856 | enif_free_env(req.data.term.env); 857 | return return_zmq_errno(env, ETERM); 858 | } 859 | else if (zmq_sendmsg(context->thread_socket, &msg, 0) == -1) { 860 | enif_mutex_unlock(context->mutex); 861 | zmq_msg_close(&msg); 862 | enif_free_env(req.data.term.env); 863 | return return_zmq_errno(env, zmq_errno()); 864 | } 865 | else { 866 | enif_mutex_unlock(context->mutex); 867 | zmq_msg_close(&msg); 868 | // thread has a reference to the context, decrement here 869 | enif_release_resource(context); 870 | return enif_make_copy(env, req.data.term.ref); 871 | } 872 | } 873 | 874 | NIF(erlzmq_nif_version) 875 | { 876 | int major, minor, patch; 877 | zmq_version(&major, &minor, &patch); 878 | return enif_make_tuple3(env, enif_make_int(env, major), 879 | enif_make_int(env, minor), 880 | enif_make_int(env, patch)); 881 | } 882 | 883 | static void * polling_thread(void * handle) 884 | { 885 | erlzmq_context_t * context = (erlzmq_context_t *) handle; 886 | enif_keep_resource(context); 887 | 888 | void * thread_socket = zmq_socket(context->context_zmq, ZMQ_PULL); 889 | assert(thread_socket); 890 | int status = zmq_connect(thread_socket, context->thread_socket_name); 891 | assert(status == 0); 892 | 893 | vector_t items_zmq; 894 | status = vector_initialize_pow2(zmq_pollitem_t, &items_zmq, 1, 895 | ERLZMQ_MAX_CONCURRENT_REQUESTS); 896 | assert(status == 0); 897 | zmq_pollitem_t thread_socket_poll_zmq = {thread_socket, 0, ZMQ_POLLIN, 0}; 898 | status = vector_append(zmq_pollitem_t, &items_zmq, &thread_socket_poll_zmq); 899 | assert(status == 0); 900 | 901 | vector_t requests; 902 | status = vector_initialize_pow2(erlzmq_thread_request_t, &requests, 1, 903 | ERLZMQ_MAX_CONCURRENT_REQUESTS); 904 | assert(status == 0); 905 | erlzmq_thread_request_t request_empty; 906 | memset(&request_empty, 0, sizeof(erlzmq_thread_request_t)); 907 | status = vector_append(erlzmq_thread_request_t, &requests, &request_empty); 908 | assert(status == 0); 909 | 910 | int i; 911 | for (;;) { 912 | int count = zmq_poll(vector_p(zmq_pollitem_t, &items_zmq), 913 | vector_count(&items_zmq), -1); 914 | assert(count != -1); 915 | if (vector_get(zmq_pollitem_t, &items_zmq, 0)->revents & ZMQ_POLLIN) { 916 | --count; 917 | } 918 | for (i = 1; i < vector_count(&items_zmq) && count > 0; ++i) { 919 | zmq_pollitem_t * item = vector_get(zmq_pollitem_t, &items_zmq, i); 920 | erlzmq_thread_request_t * r = vector_get(erlzmq_thread_request_t, 921 | &requests, i); 922 | if (item->revents & ZMQ_POLLIN) { 923 | assert(r->type == ERLZMQ_THREAD_REQUEST_RECV); 924 | --count; 925 | item->revents = 0; 926 | 927 | zmq_msg_t msg; 928 | if (zmq_msg_init(&msg)) { 929 | fprintf(stderr, "zmq_msg_init error: %s\n", 930 | strerror(zmq_errno())); 931 | assert(0); 932 | } 933 | int keep_socket = 0; 934 | assert(r->data.recv.socket->mutex); 935 | enif_mutex_lock(r->data.recv.socket->mutex); 936 | if (zmq_recvmsg(r->data.recv.socket->socket_zmq, &msg, 937 | r->data.recv.flags) == -1) { 938 | enif_mutex_unlock(r->data.recv.socket->mutex); 939 | zmq_msg_close(&msg); 940 | int const error = zmq_errno(); 941 | if (r->data.recv.socket->active == ERLZMQ_SOCKET_ACTIVE_ON && 942 | error == EAGAIN) { 943 | keep_socket = 1; 944 | } 945 | else if (r->data.recv.socket->active == ERLZMQ_SOCKET_ACTIVE_ON) { 946 | enif_send(NULL, &r->data.recv.socket->active_pid, r->data.recv.env, 947 | enif_make_tuple3(r->data.recv.env, 948 | enif_make_atom(r->data.recv.env, "zmq"), 949 | enif_make_tuple2(r->data.recv.env, 950 | enif_make_uint64(r->data.recv.env, 951 | r->data.recv.socket->socket_index), 952 | enif_make_resource(r->data.recv.env, r->data.recv.socket)), 953 | return_zmq_errno(r->data.recv.env, error))); 954 | } 955 | else { 956 | // an EAGAIN error could occur if a timeout is set on the socket 957 | enif_send(NULL, &r->data.recv.pid, r->data.recv.env, 958 | enif_make_tuple2(r->data.recv.env, 959 | enif_make_copy(r->data.recv.env, r->data.recv.ref), 960 | return_zmq_errno(r->data.recv.env, error))); 961 | } 962 | } 963 | else { 964 | enif_mutex_unlock(r->data.recv.socket->mutex); 965 | 966 | ErlNifBinary binary; 967 | enif_alloc_binary(zmq_msg_size(&msg), &binary); 968 | memcpy(binary.data, zmq_msg_data(&msg), zmq_msg_size(&msg)); 969 | zmq_msg_close(&msg); 970 | 971 | if (r->data.recv.socket->active == ERLZMQ_SOCKET_ACTIVE_ON) { 972 | ERL_NIF_TERM flags_list; 973 | 974 | // Should we send the multipart flag 975 | size_t value_len = sizeof(int64_t); 976 | int64_t flag_value = 0; 977 | if (zmq_getsockopt(r->data.recv.socket->socket_zmq, 978 | ZMQ_RCVMORE, &flag_value, &value_len)) { 979 | fprintf(stderr, "zmq_getsockopt error: %s\n", 980 | strerror(zmq_errno())); 981 | assert(0); 982 | } 983 | if(flag_value == 1) { 984 | flags_list = enif_make_list1(r->data.recv.env, 985 | enif_make_atom(r->data.recv.env, 986 | "rcvmore")); 987 | } else { 988 | flags_list = enif_make_list(r->data.recv.env, 0); 989 | } 990 | 991 | enif_send(NULL, &r->data.recv.socket->active_pid, r->data.recv.env, 992 | enif_make_tuple4(r->data.recv.env, 993 | enif_make_atom(r->data.recv.env, "zmq"), 994 | enif_make_tuple2(r->data.recv.env, 995 | enif_make_uint64(r->data.recv.env, 996 | r->data.recv.socket->socket_index), 997 | enif_make_resource(r->data.recv.env, r->data.recv.socket)), 998 | enif_make_binary(r->data.recv.env, &binary), 999 | flags_list)); 1000 | keep_socket = 1; 1001 | } 1002 | else { 1003 | enif_send(NULL, &r->data.recv.pid, r->data.recv.env, 1004 | enif_make_tuple2(r->data.recv.env, 1005 | enif_make_copy(r->data.recv.env, r->data.recv.ref), 1006 | enif_make_binary(r->data.recv.env, &binary))); 1007 | } 1008 | } 1009 | if (keep_socket) { 1010 | enif_clear_env(r->data.recv.env); 1011 | } 1012 | else { 1013 | enif_free_env(r->data.recv.env); 1014 | enif_release_resource(r->data.recv.socket); 1015 | 1016 | status = vector_remove(&items_zmq, i); 1017 | assert(status == 0); 1018 | status = vector_remove(&requests, i); 1019 | assert(status == 0); 1020 | --i; 1021 | } 1022 | } 1023 | else if (item->revents & ZMQ_POLLOUT) { 1024 | assert(r->type == ERLZMQ_THREAD_REQUEST_SEND); 1025 | --count; 1026 | item->revents = 0; 1027 | 1028 | assert(r->data.send.socket->mutex); 1029 | enif_mutex_lock(r->data.send.socket->mutex); 1030 | if (zmq_sendmsg(r->data.send.socket->socket_zmq, 1031 | &r->data.send.msg, r->data.send.flags) == -1) { 1032 | enif_mutex_unlock(r->data.send.socket->mutex); 1033 | enif_send(NULL, &r->data.send.pid, r->data.send.env, 1034 | enif_make_tuple2(r->data.send.env, 1035 | enif_make_copy(r->data.send.env, r->data.send.ref), 1036 | return_zmq_errno(r->data.send.env, zmq_errno()))); 1037 | } else { 1038 | enif_mutex_unlock(r->data.send.socket->mutex); 1039 | enif_send(NULL, &r->data.send.pid, r->data.send.env, 1040 | enif_make_tuple2(r->data.send.env, 1041 | enif_make_copy(r->data.send.env, r->data.send.ref), 1042 | enif_make_atom(r->data.send.env, "ok"))); 1043 | } 1044 | zmq_msg_close(&r->data.send.msg); 1045 | enif_free_env(r->data.send.env); 1046 | enif_release_resource(r->data.send.socket); 1047 | 1048 | status = vector_remove(&items_zmq, i); 1049 | assert(status == 0); 1050 | status = vector_remove(&requests, i); 1051 | assert(status == 0); 1052 | --i; 1053 | } 1054 | else { 1055 | assert(item->revents == 0); 1056 | } 1057 | } 1058 | 1059 | // incoming requests to poll on 1060 | if (vector_get(zmq_pollitem_t, &items_zmq, 0)->revents & ZMQ_POLLIN) { 1061 | vector_get(zmq_pollitem_t, &items_zmq, 0)->revents = 0; 1062 | zmq_msg_t msg; 1063 | if (zmq_msg_init(&msg)) { 1064 | fprintf(stderr, "zmq_msg_init error: %s\n", 1065 | strerror(zmq_errno())); 1066 | assert(0); 1067 | } 1068 | 1069 | assert(context->mutex); 1070 | enif_mutex_lock(context->mutex); 1071 | status = zmq_recvmsg(thread_socket, &msg, 0); 1072 | enif_mutex_unlock(context->mutex); 1073 | assert(status != -1); 1074 | 1075 | assert(zmq_msg_size(&msg) == sizeof(erlzmq_thread_request_t)); 1076 | 1077 | erlzmq_thread_request_t * r = 1078 | (erlzmq_thread_request_t *) zmq_msg_data(&msg); 1079 | ErlNifMutex * mutex = 0; 1080 | if (r->type == ERLZMQ_THREAD_REQUEST_SEND) { 1081 | zmq_pollitem_t item_zmq = {r->data.send.socket->socket_zmq, 1082 | 0, ZMQ_POLLOUT, 0}; 1083 | status = vector_append(zmq_pollitem_t, &items_zmq, &item_zmq); 1084 | assert(status == 0); 1085 | status = vector_append(erlzmq_thread_request_t, &requests, r); 1086 | assert(status == 0); 1087 | zmq_msg_close(&msg); 1088 | } 1089 | else if (r->type == ERLZMQ_THREAD_REQUEST_RECV) { 1090 | zmq_pollitem_t item_zmq = {r->data.recv.socket->socket_zmq, 1091 | 0, ZMQ_POLLIN, 0}; 1092 | status = vector_append(zmq_pollitem_t, &items_zmq, &item_zmq); 1093 | assert(status == 0); 1094 | status = vector_append(erlzmq_thread_request_t, &requests, r); 1095 | assert(status == 0); 1096 | zmq_msg_close(&msg); 1097 | } 1098 | else if (r->type == ERLZMQ_THREAD_REQUEST_CLOSE) { 1099 | if (! r->data.close.socket->mutex) 1100 | { 1101 | enif_send(NULL, &r->data.close.pid, r->data.close.env, 1102 | enif_make_tuple2(r->data.close.env, 1103 | enif_make_copy(r->data.close.env, r->data.close.ref), 1104 | return_zmq_errno(r->data.close.env, ETERM))); 1105 | enif_free_env(r->data.close.env); 1106 | zmq_msg_close(&msg); 1107 | continue; 1108 | } 1109 | enif_mutex_lock(r->data.close.socket->mutex); 1110 | assert(r->data.close.socket->socket_zmq); 1111 | 1112 | // remove all entries with this socket 1113 | for (i = vector_count(&items_zmq) - 1; i > 0; --i) { 1114 | zmq_pollitem_t * item = vector_get(zmq_pollitem_t, &items_zmq, i); 1115 | if (item->socket == r->data.close.socket->socket_zmq) { 1116 | erlzmq_thread_request_t * r_old = 1117 | vector_get(erlzmq_thread_request_t, &requests, i); 1118 | if (r_old->type == ERLZMQ_THREAD_REQUEST_RECV) { 1119 | enif_free_env(r_old->data.recv.env); 1120 | enif_release_resource(r_old->data.recv.socket); 1121 | } 1122 | else if (r_old->type == ERLZMQ_THREAD_REQUEST_SEND) { 1123 | zmq_msg_close(&(r_old->data.send.msg)); 1124 | enif_free_env(r_old->data.send.env); 1125 | enif_release_resource(r_old->data.send.socket); 1126 | } 1127 | else { 1128 | assert(0); 1129 | } 1130 | status = vector_remove(&items_zmq, i); 1131 | assert(status == 0); 1132 | status = vector_remove(&requests, i); 1133 | assert(status == 0); 1134 | } 1135 | } 1136 | // close the socket 1137 | zmq_close(r->data.close.socket->socket_zmq); 1138 | r->data.close.socket->socket_zmq = 0; 1139 | mutex = r->data.close.socket->mutex; 1140 | r->data.close.socket->mutex = 0; 1141 | enif_mutex_unlock(mutex); 1142 | enif_mutex_destroy(mutex); 1143 | enif_release_resource(r->data.close.socket); 1144 | // notify the waiting request 1145 | enif_send(NULL, &r->data.close.pid, r->data.close.env, 1146 | enif_make_tuple2(r->data.close.env, 1147 | enif_make_copy(r->data.close.env, r->data.close.ref), 1148 | enif_make_atom(r->data.close.env, "ok"))); 1149 | enif_free_env(r->data.close.env); 1150 | zmq_msg_close(&msg); 1151 | } 1152 | else if (r->type == ERLZMQ_THREAD_REQUEST_TERM) { 1153 | if (! context->mutex) 1154 | { 1155 | enif_send(NULL, &r->data.term.pid, r->data.term.env, 1156 | enif_make_tuple2(r->data.term.env, 1157 | enif_make_copy(r->data.term.env, r->data.term.ref), 1158 | return_zmq_errno(r->data.term.env, ETERM))); 1159 | enif_free_env(r->data.term.env); 1160 | zmq_msg_close(&msg); 1161 | continue; 1162 | } 1163 | enif_mutex_lock(context->mutex); 1164 | free(context->thread_socket_name); 1165 | // use this to flag context is over 1166 | context->thread_socket_name = 0; 1167 | // cleanup pending requests 1168 | for (i = 1; i < vector_count(&requests); ++i) { 1169 | erlzmq_thread_request_t * r_old = vector_get(erlzmq_thread_request_t, 1170 | &requests, i); 1171 | if (r_old->type == ERLZMQ_THREAD_REQUEST_RECV) 1172 | { 1173 | enif_free_env(r_old->data.recv.env); 1174 | if (r_old->data.recv.socket->mutex) { 1175 | enif_mutex_lock(r_old->data.recv.socket->mutex); 1176 | assert(r_old->data.recv.socket->socket_zmq); 1177 | zmq_close(r_old->data.recv.socket->socket_zmq); 1178 | r_old->data.recv.socket->socket_zmq = 0; 1179 | mutex = r_old->data.recv.socket->mutex; 1180 | r_old->data.recv.socket->mutex = 0; 1181 | enif_mutex_unlock(mutex); 1182 | enif_mutex_destroy(mutex); 1183 | } 1184 | enif_release_resource(r_old->data.recv.socket); 1185 | } 1186 | else if (r_old->type == ERLZMQ_THREAD_REQUEST_SEND) { 1187 | zmq_msg_close(&r_old->data.send.msg); 1188 | enif_free_env(r_old->data.send.env); 1189 | if (r_old->data.send.socket->mutex) { 1190 | enif_mutex_lock(r_old->data.send.socket->mutex); 1191 | assert(r_old->data.send.socket->socket_zmq); 1192 | zmq_close(r_old->data.send.socket->socket_zmq); 1193 | r_old->data.send.socket->socket_zmq = 0; 1194 | mutex = r_old->data.send.socket->mutex; 1195 | r_old->data.send.socket->mutex = 0; 1196 | enif_mutex_unlock(mutex); 1197 | enif_mutex_destroy(mutex); 1198 | } 1199 | enif_release_resource(r_old->data.send.socket); 1200 | } 1201 | } 1202 | // terminate the context 1203 | zmq_close(thread_socket); 1204 | zmq_close(context->thread_socket); 1205 | mutex = context->mutex; 1206 | context->mutex = 0; 1207 | enif_mutex_unlock(mutex); 1208 | enif_mutex_destroy(mutex); 1209 | void * const context_term = context->context_zmq; 1210 | enif_release_resource(context); 1211 | 1212 | // notify the waiting request 1213 | enif_send(NULL, &r->data.term.pid, r->data.term.env, 1214 | enif_make_tuple2(r->data.term.env, 1215 | enif_make_copy(r->data.term.env, r->data.term.ref), 1216 | enif_make_atom(r->data.term.env, "ok"))); 1217 | enif_free_env(r->data.term.env); 1218 | zmq_msg_close(&msg); 1219 | vector_destroy(&items_zmq); 1220 | vector_destroy(&requests); 1221 | // the thread will block here until all sockets 1222 | // within the context are closed 1223 | zmq_term(context_term); 1224 | return NULL; 1225 | } 1226 | else { 1227 | fprintf(stderr, "invalid request type: %d\n", r->type); 1228 | assert(0); 1229 | } 1230 | } 1231 | } 1232 | return NULL; 1233 | } 1234 | 1235 | static ERL_NIF_TERM add_active_req(ErlNifEnv* env, erlzmq_socket_t * socket) 1236 | { 1237 | erlzmq_thread_request_t req; 1238 | req.type = ERLZMQ_THREAD_REQUEST_RECV; 1239 | req.data.recv.env = enif_alloc_env(); 1240 | req.data.recv.flags = 0; 1241 | enif_self(env, &req.data.recv.pid); 1242 | req.data.recv.socket = socket; 1243 | 1244 | zmq_msg_t msg; 1245 | if (zmq_msg_init_size(&msg, sizeof(erlzmq_thread_request_t))) { 1246 | zmq_msg_close(&msg); 1247 | enif_free_env(req.data.recv.env); 1248 | return return_zmq_errno(env, zmq_errno()); 1249 | } 1250 | 1251 | memcpy(zmq_msg_data(&msg), &req, sizeof(erlzmq_thread_request_t)); 1252 | 1253 | if (! socket->context->mutex) { 1254 | zmq_msg_close(&msg); 1255 | enif_free_env(req.data.recv.env); 1256 | return return_zmq_errno(env, ETERM); 1257 | } 1258 | enif_mutex_lock(socket->context->mutex); 1259 | if (! socket->context->thread_socket_name) { 1260 | if (socket->context->mutex) { 1261 | enif_mutex_unlock(socket->context->mutex); 1262 | } 1263 | zmq_msg_close(&msg); 1264 | enif_free_env(req.data.recv.env); 1265 | return return_zmq_errno(env, ETERM); 1266 | } 1267 | else if (zmq_sendmsg(socket->context->thread_socket, &msg, 0) == -1) { 1268 | enif_mutex_unlock(socket->context->mutex); 1269 | zmq_msg_close(&msg); 1270 | enif_free_env(req.data.recv.env); 1271 | return return_zmq_errno(env, zmq_errno()); 1272 | } 1273 | else { 1274 | enif_mutex_unlock(socket->context->mutex); 1275 | zmq_msg_close(&msg); 1276 | // each pointer to the socket in a request increments the reference 1277 | enif_keep_resource(socket); 1278 | return enif_make_atom(env, "ok"); 1279 | } 1280 | } 1281 | 1282 | static ERL_NIF_TERM return_zmq_errno(ErlNifEnv* env, int const value) 1283 | { 1284 | switch (value) { 1285 | case EPERM: 1286 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1287 | enif_make_atom(env, "eperm")); 1288 | case ENOENT: 1289 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1290 | enif_make_atom(env, "enoent")); 1291 | case ESRCH: 1292 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1293 | enif_make_atom(env, "esrch")); 1294 | case EINTR: 1295 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1296 | enif_make_atom(env, "eintr")); 1297 | case EIO: 1298 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1299 | enif_make_atom(env, "eio")); 1300 | case ENXIO: 1301 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1302 | enif_make_atom(env, "enxio")); 1303 | case ENOEXEC: 1304 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1305 | enif_make_atom(env, "enoexec")); 1306 | case EBADF: 1307 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1308 | enif_make_atom(env, "ebadf")); 1309 | case ECHILD: 1310 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1311 | enif_make_atom(env, "echild")); 1312 | case EDEADLK: 1313 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1314 | enif_make_atom(env, "edeadlk")); 1315 | case ENOMEM: 1316 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1317 | enif_make_atom(env, "enomem")); 1318 | case EACCES: 1319 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1320 | enif_make_atom(env, "eacces")); 1321 | case EFAULT: 1322 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1323 | enif_make_atom(env, "efault")); 1324 | case ENOTBLK: 1325 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1326 | enif_make_atom(env, "enotblk")); 1327 | case EBUSY: 1328 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1329 | enif_make_atom(env, "ebusy")); 1330 | case EEXIST: 1331 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1332 | enif_make_atom(env, "eexist")); 1333 | case EXDEV: 1334 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1335 | enif_make_atom(env, "exdev")); 1336 | case ENODEV: 1337 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1338 | enif_make_atom(env, "enodev")); 1339 | case ENOTDIR: 1340 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1341 | enif_make_atom(env, "enotdir")); 1342 | case EISDIR: 1343 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1344 | enif_make_atom(env, "eisdir")); 1345 | case EINVAL: 1346 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1347 | enif_make_atom(env, "einval")); 1348 | case ENFILE: 1349 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1350 | enif_make_atom(env, "enfile")); 1351 | case EMFILE: 1352 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1353 | enif_make_atom(env, "emfile")); 1354 | case ETXTBSY: 1355 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1356 | enif_make_atom(env, "etxtbsy")); 1357 | case EFBIG: 1358 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1359 | enif_make_atom(env, "efbig")); 1360 | case ENOSPC: 1361 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1362 | enif_make_atom(env, "enospc")); 1363 | case ESPIPE: 1364 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1365 | enif_make_atom(env, "espipe")); 1366 | case EROFS: 1367 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1368 | enif_make_atom(env, "erofs")); 1369 | case EMLINK: 1370 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1371 | enif_make_atom(env, "emlink")); 1372 | case EPIPE: 1373 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1374 | enif_make_atom(env, "epipe")); 1375 | case EAGAIN: 1376 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1377 | enif_make_atom(env, "eagain")); 1378 | case EINPROGRESS: 1379 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1380 | enif_make_atom(env, "einprogress")); 1381 | case EALREADY: 1382 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1383 | enif_make_atom(env, "ealready")); 1384 | case ENOTSOCK: 1385 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1386 | enif_make_atom(env, "enotsock")); 1387 | case EDESTADDRREQ: 1388 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1389 | enif_make_atom(env, "edestaddrreq")); 1390 | case EMSGSIZE: 1391 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1392 | enif_make_atom(env, "emsgsize")); 1393 | case EPROTOTYPE: 1394 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1395 | enif_make_atom(env, "eprototype")); 1396 | case ENOPROTOOPT: 1397 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1398 | enif_make_atom(env, "eprotoopt")); 1399 | case EPROTONOSUPPORT: 1400 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1401 | enif_make_atom(env, "eprotonosupport")); 1402 | case ESOCKTNOSUPPORT: 1403 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1404 | enif_make_atom(env, "esocktnosupport")); 1405 | case ENOTSUP: 1406 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1407 | enif_make_atom(env, "enotsup")); 1408 | case EPFNOSUPPORT: 1409 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1410 | enif_make_atom(env, "epfnosupport")); 1411 | case EAFNOSUPPORT: 1412 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1413 | enif_make_atom(env, "eafnosupport")); 1414 | case EADDRINUSE: 1415 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1416 | enif_make_atom(env, "eaddrinuse")); 1417 | case EADDRNOTAVAIL: 1418 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1419 | enif_make_atom(env, "eaddrnotavail")); 1420 | case ENETDOWN: 1421 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1422 | enif_make_atom(env, "enetdown")); 1423 | case ENETUNREACH: 1424 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1425 | enif_make_atom(env, "enetunreach")); 1426 | case ENETRESET: 1427 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1428 | enif_make_atom(env, "enetreset")); 1429 | case ECONNABORTED: 1430 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1431 | enif_make_atom(env, "econnaborted")); 1432 | case ECONNRESET: 1433 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1434 | enif_make_atom(env, "econnreset")); 1435 | case ENOBUFS: 1436 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1437 | enif_make_atom(env, "enobufs")); 1438 | case EISCONN: 1439 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1440 | enif_make_atom(env, "eisconn")); 1441 | case ENOTCONN: 1442 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1443 | enif_make_atom(env, "enotconn")); 1444 | case ESHUTDOWN: 1445 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1446 | enif_make_atom(env, "eshutdown")); 1447 | case ETOOMANYREFS: 1448 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1449 | enif_make_atom(env, "etoomanyrefs")); 1450 | case ETIMEDOUT: 1451 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1452 | enif_make_atom(env, "etimedout")); 1453 | case ECONNREFUSED: 1454 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1455 | enif_make_atom(env, "econnrefused")); 1456 | case ELOOP: 1457 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1458 | enif_make_atom(env, "eloop")); 1459 | case ENAMETOOLONG: 1460 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1461 | enif_make_atom(env, "enametoolong")); 1462 | case (ZMQ_HAUSNUMERO + 1): 1463 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1464 | enif_make_atom(env, "enotsup")); 1465 | case (ZMQ_HAUSNUMERO + 2): 1466 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1467 | enif_make_atom(env, "eprotonosupport")); 1468 | case (ZMQ_HAUSNUMERO + 3): 1469 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1470 | enif_make_atom(env, "enobufs")); 1471 | case (ZMQ_HAUSNUMERO + 4): 1472 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1473 | enif_make_atom(env, "enetdown")); 1474 | case (ZMQ_HAUSNUMERO + 5): 1475 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1476 | enif_make_atom(env, "eaddrinuse")); 1477 | case (ZMQ_HAUSNUMERO + 6): 1478 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1479 | enif_make_atom(env, "eaddrnotavail")); 1480 | case (ZMQ_HAUSNUMERO + 7): 1481 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1482 | enif_make_atom(env, "econnrefused")); 1483 | case (ZMQ_HAUSNUMERO + 8): 1484 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1485 | enif_make_atom(env, "einprogress")); 1486 | case (ZMQ_HAUSNUMERO + 51): 1487 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1488 | enif_make_atom(env, "efsm")); 1489 | case (ZMQ_HAUSNUMERO + 52): 1490 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1491 | enif_make_atom(env, "enocompatproto")); 1492 | case (ZMQ_HAUSNUMERO + 53): 1493 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1494 | enif_make_atom(env, "eterm")); 1495 | case (ZMQ_HAUSNUMERO + 54): 1496 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1497 | enif_make_atom(env, "emthread")); 1498 | default: 1499 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 1500 | enif_make_int(env, value)); 1501 | } 1502 | } 1503 | 1504 | static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) 1505 | { 1506 | erlzmq_nif_resource_context = 1507 | enif_open_resource_type(env, "erlzmq_nif", 1508 | "erlzmq_nif_resource_context", 1509 | NULL, 1510 | ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 1511 | 0); 1512 | erlzmq_nif_resource_socket = 1513 | enif_open_resource_type(env, "erlzmq_nif", 1514 | "erlzmq_nif_resource_socket", 1515 | NULL, 1516 | ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER, 1517 | 0); 1518 | return 0; 1519 | } 1520 | 1521 | static void on_unload(ErlNifEnv* env, void* priv_data) { 1522 | } 1523 | 1524 | ERL_NIF_INIT(erlzmq_nif, nif_funcs, &on_load, NULL, NULL, &on_unload); 1525 | 1526 | -------------------------------------------------------------------------------- /c_src/vector.c: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8; Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- 2 | * ex: set softtabstop=4 tabstop=4 shiftwidth=4 expandtab fileencoding=utf-8: 3 | * 4 | * BSD LICENSE 5 | * 6 | * Copyright (c) 2011, Michael Truog 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * * All advertising materials mentioning features or use of this 19 | * software must display the following acknowledgment: 20 | * This product includes software developed by Michael Truog 21 | * * The name of the author may not be used to endorse or promote 22 | * products derived from this software without specific prior 23 | * written permission 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 26 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 27 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 28 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 30 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 35 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 36 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 38 | * DAMAGE. 39 | */ 40 | #include "vector.h" 41 | #include 42 | #include 43 | 44 | static size_t greater_pow2(size_t size); 45 | 46 | int vector_initialize(vector_t * v, 47 | size_t allocation_increment, 48 | size_t allocation_size, 49 | size_t allocation_size_max, 50 | size_t element_size) 51 | { 52 | v->allocation_size = greater_pow2(allocation_size); 53 | v->allocation_increment = greater_pow2(allocation_increment); 54 | v->allocation_size_max = allocation_size_max; 55 | v->p = malloc(v->allocation_size); 56 | v->element_size = element_size; 57 | v->count = 0; 58 | if (v->p) 59 | return VECTOR_SUCCESS; 60 | else 61 | return VECTOR_FAILURE; 62 | } 63 | 64 | void vector_destroy(vector_t * v) 65 | { 66 | free(v->p); 67 | } 68 | 69 | int vector_reserve(vector_t * v, size_t count) 70 | { 71 | size_t const size = v->element_size * count; 72 | size_t new_size = v->allocation_size; 73 | void * tmp; 74 | if (size < v->allocation_size) 75 | return VECTOR_SUCCESS; 76 | if (size > v->allocation_size_max) 77 | return VECTOR_FAILURE; 78 | if (v->allocation_increment == 0) { 79 | while (size > new_size) 80 | new_size <<= 1; 81 | } 82 | else { 83 | while (size > new_size) 84 | new_size += v->allocation_increment; 85 | } 86 | if (new_size > v->allocation_size_max) 87 | new_size = v->allocation_size_max; 88 | tmp = realloc(v->p, new_size); 89 | if (! tmp) 90 | return VECTOR_FAILURE; 91 | v->p = tmp; 92 | v->allocation_size = new_size; 93 | return VECTOR_SUCCESS; 94 | } 95 | 96 | int vector_copy(vector_t * v_dst, vector_t * v_src, 97 | size_t i_src, size_t count_src, size_t i_dst) 98 | { 99 | if (v_dst->element_size != v_src->element_size) 100 | return VECTOR_FAILURE; 101 | if (count_src == 0) 102 | count_src = v_src->count; 103 | if (vector_reserve(v_dst, i_dst + count_src) == VECTOR_FAILURE) 104 | return VECTOR_FAILURE; 105 | memcpy(&(((char *) v_dst->p)[i_dst * v_dst->element_size]), 106 | &(((char *) v_src->p)[i_src * v_dst->element_size]), 107 | count_src * v_dst->element_size); 108 | if ((i_dst + count_src) > v_dst->count) 109 | v_dst->count = i_dst + count_src; 110 | return VECTOR_SUCCESS; 111 | } 112 | 113 | int vector_move(vector_t * v, size_t i_dst, size_t i_src, size_t count_src) 114 | { 115 | if (count_src == 0) 116 | count_src = v->count - i_src; 117 | if (vector_reserve(v, i_dst + count_src) == VECTOR_FAILURE) 118 | return VECTOR_FAILURE; 119 | memmove(&(((char *) v->p)[i_dst * v->element_size]), 120 | &(((char *) v->p)[i_src * v->element_size]), 121 | count_src * v->element_size); 122 | if ((i_dst + count_src) > v->count) 123 | v->count = i_dst + count_src; 124 | return VECTOR_SUCCESS; 125 | } 126 | 127 | int vector_append_element(vector_t * v, void * p, size_t size) 128 | { 129 | if (v->element_size != size) 130 | return VECTOR_FAILURE; 131 | if (vector_reserve(v, v->count + 1) == VECTOR_FAILURE) 132 | return VECTOR_FAILURE; 133 | memcpy(&(((char *) v->p)[v->count * v->element_size]), p, size); 134 | ++(v->count); 135 | return VECTOR_SUCCESS; 136 | } 137 | 138 | int vector_remove(vector_t * v, size_t i) 139 | { 140 | if ((i + 1) == v->count) { 141 | --(v->count); 142 | return VECTOR_SUCCESS; 143 | } 144 | else { 145 | if (vector_move(v, i, i + 1, 0) == VECTOR_FAILURE) 146 | return VECTOR_FAILURE; 147 | --(v->count); 148 | return VECTOR_SUCCESS; 149 | } 150 | } 151 | 152 | /* find a value >= totalSize as a power of 2 153 | */ 154 | static size_t greater_pow2(size_t total_size) 155 | { 156 | int bits = 0; 157 | size_t div2 = total_size; 158 | size_t value; 159 | for (; div2 > 1; div2 >>= 1) 160 | bits++; 161 | value = (1 << bits); 162 | if (value == total_size) 163 | return value; 164 | else 165 | return (value << 1); 166 | } 167 | 168 | -------------------------------------------------------------------------------- /c_src/vector.h: -------------------------------------------------------------------------------- 1 | /* -*- coding: utf-8; Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- 2 | * ex: set softtabstop=4 tabstop=4 shiftwidth=4 expandtab fileencoding=utf-8: 3 | * 4 | * BSD LICENSE 5 | * 6 | * Copyright (c) 2011, Michael Truog 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * * All advertising materials mentioning features or use of this 19 | * software must display the following acknowledgment: 20 | * This product includes software developed by Michael Truog 21 | * * The name of the author may not be used to endorse or promote 22 | * products derived from this software without specific prior 23 | * written permission 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 26 | * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 27 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 28 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 30 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 35 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 36 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 38 | * DAMAGE. 39 | */ 40 | #ifndef VECTOR_H 41 | #define VECTOR_H 42 | 43 | #include 44 | 45 | typedef struct { 46 | 47 | size_t allocation_increment; 48 | size_t allocation_size; 49 | size_t allocation_size_max; 50 | void * p; 51 | 52 | size_t element_size; 53 | size_t count; 54 | 55 | } vector_t; 56 | 57 | #define vector_initialize_linear(TYPE, V, COUNT_INCR, COUNT, COUNT_MAX) \ 58 | vector_initialize(V, COUNT_INCR * sizeof(TYPE), COUNT * sizeof(TYPE), \ 59 | COUNT_MAX * sizeof(TYPE), sizeof(TYPE)) 60 | #define vector_initialize_pow2(TYPE, V, COUNT, COUNT_MAX) \ 61 | vector_initialize(V, 0, COUNT * sizeof(TYPE), \ 62 | COUNT_MAX * sizeof(TYPE), sizeof(TYPE)) 63 | int vector_initialize(vector_t * v, 64 | size_t allocation_increment, 65 | size_t allocation_size, 66 | size_t allocation_size_max, 67 | size_t element_size); 68 | void vector_destroy(vector_t * v); 69 | int vector_reserve(vector_t * v, size_t count); 70 | #define vector_copy_all(DST, SRC) vector_copy(DST, SRC, 0, 0, 0) 71 | int vector_copy(vector_t * v_dst, vector_t * v_src, 72 | size_t i_src, size_t count_src, size_t i_dst); 73 | int vector_move(vector_t * v, size_t i_dst, size_t i_src, size_t count_src); 74 | #define vector_append(TYPE, V, OBJ) \ 75 | vector_append_element(V, OBJ, sizeof(TYPE)) 76 | int vector_append_element(vector_t * v, void * p, size_t size); 77 | int vector_remove(vector_t * v, size_t i); 78 | #define vector_has(V, I) ((I) < (V)->count) 79 | #define vector_p(TYPE, V) ((TYPE *) ((V)->p)) 80 | #define vector_get(TYPE, V, I) (&(vector_p(TYPE, V)[I])) 81 | #define vector_count(V) ((V)->count) 82 | #define vector_size(V) ((V)->allocation_size) 83 | 84 | #define VECTOR_FAILURE -1 85 | #define VECTOR_SUCCESS 0 86 | 87 | #endif /* VECTOR_H */ 88 | 89 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @title NIF based Erlang bindings for the ZeroMQ messaging library. 2 | @copyright 2011 Yurii Rashkovskii, Evax Sofware and Michael Truog 3 | @author Yurii Rashkovskii [http://rashkovskii.com] 4 | @author Evax Software [http://www.evax.fr] 5 | @author Michael Truog [http://mjtruog.veryspeedy.net] 6 | @reference The ZeroMQ messaging library. 7 | @doc 8 | 9 |
    10 |
  1. {@section Overview}
  2. 11 |
  3. {@section Downloading}
  4. 12 |
  5. {@section Building}
  6. 13 |
  7. {@section Architecture}
  8. 14 |
  9. {@section License}
  10. 15 |
16 | 17 | == Overview == 18 | 19 | The erlzmq2 application provides high-performance NIF based Erlang bindings 20 | for the ZeroMQ messaging library. 21 | 22 | == Downloading == 23 | 24 | The erlzmq2 source code can be found on [http://github.com/zeromq/erlzmq2 GitHub] 25 | 26 | ``` 27 | $ git clone http://github.com/zeromq/erlzmq2.git 28 | ''' 29 | 30 | It is also available on [http://erlagner.org/ Agner]: 31 | 32 | ``` 33 | $ agner build erlzmq 34 | ''' 35 | 36 | In order to build erlzmq2 against a specific version of ZeroMQ (not 'v2.1.4'), use this: 37 | 38 | ``` 39 | $ ZEROMQ_VERSION=v agner build erlzmq 40 | ''' 41 | 42 | == Building == 43 | 44 | Build the code 45 | ``` 46 | $ make 47 | ''' 48 | 49 | If you want to build against a specific version of ZeroMQ (not 'v2.1.4'), use this: 50 | 51 | ``` 52 | $ ZEROMQ_VERSION=v make 53 | ''' 54 | 55 | Build the docs 56 | ``` 57 | $ make docs 58 | ''' 59 | 60 | Run the test suite 61 | ``` 62 | $ make test 63 | ''' 64 | 65 | Run the benchmarks (requires [http://www.python.org python] and [http://matplotlib.sourceforge.net matplotlib]) 66 | ``` 67 | $ make bench 68 | ''' 69 | This will run performance tests and output png graphs in the graphs directory. 70 | 71 | Please note that to behave properly on your system ZeroMQ might require [http://www.zeromq.org/docs:tuning-zeromq some tuning]. 72 | 73 | == Architecture == 74 | 75 | The bindings use Erlang's [http://www.erlang.org/doc/man/erl_nif.html NIF (native implemented functions)] interface to achieve the best performance. One extra OS thread and one pair of inproc sockets by context are used to simulate blocking recv calls without affecting the Erlang virtual machine's responsiveness. 76 | 77 | 78 | == License == 79 | 80 | The project is released under the MIT license. 81 | 82 | -------------------------------------------------------------------------------- /include/erlzmq.hrl: -------------------------------------------------------------------------------- 1 | -define('ZMQ_PAIR', 0). 2 | -define('ZMQ_PUB', 1). 3 | -define('ZMQ_SUB', 2). 4 | -define('ZMQ_REQ', 3). 5 | -define('ZMQ_REP', 4). 6 | -define('ZMQ_DEALER', 5). 7 | -define('ZMQ_ROUTER', 6). 8 | -define('ZMQ_XREQ', ?'ZMQ_DEALER'). 9 | -define('ZMQ_XREP', ?'ZMQ_ROUTER'). 10 | -define('ZMQ_PULL', 7). 11 | -define('ZMQ_PUSH', 8). 12 | -define('ZMQ_XPUB', 9). 13 | -define('ZMQ_XSUB', 10). 14 | 15 | % ZMQ socket options. 16 | -define('ZMQ_AFFINITY', 4). 17 | -define('ZMQ_IDENTITY', 5). 18 | -define('ZMQ_SUBSCRIBE', 6). 19 | -define('ZMQ_UNSUBSCRIBE', 7). 20 | -define('ZMQ_RATE', 8). 21 | -define('ZMQ_RECOVERY_IVL', 9). 22 | -define('ZMQ_SNDBUF', 11). 23 | -define('ZMQ_RCVBUF', 12). 24 | -define('ZMQ_RCVMORE', 13). 25 | -define('ZMQ_FD', 14). 26 | -define('ZMQ_EVENTS', 15). 27 | -define('ZMQ_TYPE', 16). 28 | -define('ZMQ_LINGER', 17). 29 | -define('ZMQ_RECONNECT_IVL', 18). 30 | -define('ZMQ_BACKLOG', 19). 31 | -define('ZMQ_RECOVERY_IVL_MSEC', 20). 32 | -define('ZMQ_RECONNECT_IVL_MAX', 21). 33 | -define('ZMQ_MAXMSGSIZE', 22). 34 | -define('ZMQ_SNDHWM', 23). 35 | -define('ZMQ_RCVHWM', 24). 36 | -define('ZMQ_MULTICAST_HOPS', 25). 37 | -define('ZMQ_RCVTIMEO', 27). 38 | -define('ZMQ_SNDTIMEO', 28). 39 | -define('ZMQ_IPV4ONLY', 31). 40 | -define('ZMQ_LAST_ENDPOINT', 32). 41 | 42 | % Message options 43 | -define('ZMQ_MORE', 1). 44 | 45 | % ZMQ send/recv flags 46 | -define('ZMQ_DONTWAIT', 1). 47 | -define('ZMQ_SNDMORE', 2). 48 | 49 | %% Types 50 | 51 | %% Possible types for an erlzmq socket.
52 | %% For more information see 53 | %% zmq_socket 54 | -type erlzmq_socket_type() :: pair | pub | sub | req | rep | dealer | router | xreq | xrep | 55 | pull | push | xpub | xsub. 56 | 57 | %% The endpoint argument is a string consisting of two parts: 58 | %% transport://address
59 | %% The following transports are defined: 60 | %% inproc, ipc, tcp, pgm, epgm.
61 | %% The meaning of address is transport specific.
62 | %% For more information see 63 | %% zmq_bind or 64 | %% zmq_connect 65 | -type erlzmq_endpoint() :: string() | binary(). 66 | 67 | %% Standard error atoms. 68 | -type errno() :: eperm | enoent | srch | eintr | eio | enxio | ebad | 69 | echild | edeadlk | enomem | eacces | efault | enotblk | 70 | ebusy | eexist | exdev | enodev | enotdir | eisdir | 71 | einval | enfile | emfile | enotty | etxtbsy | efbig | 72 | enospc | espipe | erofs | emlink | epipe | eagain | 73 | einprogress | ealready | enotsock | edestaddrreq | 74 | emsgsize | eprototype | enoprotoopt | eprotonosupport | 75 | esocktnosupport | enotsup | epfnosupport | eafnosupport | 76 | eaddrinuse | eaddrnotavail | enetdown | enetunreach | 77 | enetreset | econnaborted | econnreset | enobufs | eisconn | 78 | enotconn | eshutdown | etoomanyrefs | 79 | etimedout | econnrefused | eloop | enametoolong. 80 | 81 | %% Possible error types. 82 | -type erlzmq_error_type() :: enotsup | eprotonosupport | enobufs | enetdown | 83 | eaddrinuse | eaddnotavail | econnrefused | 84 | einprogress | efsm | enocompatproto | eterm | 85 | emthread | errno() | {unknown, integer()}. 86 | 87 | %% Error tuples returned by most API functions. 88 | -type erlzmq_error() :: {error, erlzmq_error_type()}. 89 | 90 | %% Data to be sent with {@link erlzmq:send/3. send/3} or received with 91 | %% {@link erlzmq:recv/2. recv/2} 92 | -type erlzmq_data() :: iolist(). 93 | 94 | %% An opaque handle to an erlzmq context. 95 | -opaque erlzmq_context() :: binary(). 96 | 97 | %% An opaque handle to an erlzmq socket. 98 | -opaque erlzmq_socket() :: {pos_integer(), binary()}. 99 | 100 | %% The individual flags to use with {@link erlzmq:send/3. send/3} 101 | %% and {@link erlzmq:recv/2. recv/2}.
102 | %% For more information see 103 | %% zmq_send or 104 | %% zmq_recv 105 | -type erlzmq_send_recv_flag() :: dontwait | sndmore | recvmore | {timeout, timeout()}. 106 | 107 | %% A list of flags to use with {@link ezqm:send/3. send/3} and 108 | %% {@link erlzmq:recv/2. recv/2} 109 | -type erlzmq_send_recv_flags() :: list(erlzmq_send_recv_flag()). 110 | 111 | %% Available options for {@link erlzmq:setsockopt/3. setsockopt/3} 112 | %% and {@link erlzmq:getsockopt/2. getsockopt/2}.
113 | %% For more information see 114 | %% zmq_setsockopt 115 | %% and zmq_getsockopt 116 | -type erlzmq_sockopt() :: affinity | identity | subscribe | 117 | unsubscribe | rate | recovery_ivl | sndbuf | 118 | rcvbuf | rcvmore | fd | events | linger | 119 | reconnect_ivl | backlog |reconnect_ivl_max 120 | | maxmsgsize | sndhwm | rcvhwm | 121 | multicast_hops | rcvtimeo | sndtimeo | 122 | ipv4only. 123 | 124 | 125 | %% Possible option values for {@link erlzmq:setsockopt/3. setsockopt/3}. 126 | -type erlzmq_sockopt_value() :: integer() | iolist(). 127 | 128 | -------------------------------------------------------------------------------- /perf/erlzmq_perf.erl: -------------------------------------------------------------------------------- 1 | -module(erlzmq_perf). 2 | -export([local_lat_loop/3, local_lat_loop/4, 3 | remote_lat_loop/3, remote_lat_loop/4, 4 | recv_loop/2, recv_loop/3, send_loop/3]). 5 | 6 | local_lat_loop(N, S, M) -> 7 | local_lat_loop(N, S, M, passive). 8 | 9 | local_lat_loop(0, _, _, _) -> 10 | ok; 11 | local_lat_loop(N, S, M, active) -> 12 | ok = receive 13 | {zmq, S, M} -> 14 | ok 15 | end, 16 | ok = erlzmq:send(S, M), 17 | local_lat_loop(N-1, S, M, active); 18 | local_lat_loop(N, S, M, passive) -> 19 | {ok, M} = erlzmq:recv(S), 20 | ok = erlzmq:send(S, M), 21 | local_lat_loop(N-1, S, M, passive). 22 | 23 | remote_lat_loop(N, S, M) -> 24 | remote_lat_loop(N, S, M, passive). 25 | 26 | remote_lat_loop(0, _, _, _) -> 27 | ok; 28 | remote_lat_loop(N, S, M, active) -> 29 | ok = erlzmq:send(S, M), 30 | ok = receive 31 | {zmq, S, M} -> 32 | ok 33 | end, 34 | remote_lat_loop(N-1, S, M, active); 35 | 36 | remote_lat_loop(N, S, M, passive) -> 37 | ok = erlzmq:send(S, M), 38 | {ok, M} = erlzmq:recv(S), 39 | remote_lat_loop(N-1, S, M, passive). 40 | 41 | recv_loop(N, S) -> 42 | recv_loop(N, S, passive). 43 | 44 | recv_loop(0, _, _) -> 45 | ok; 46 | recv_loop(N, S, passive) -> 47 | erlzmq:recv(S), 48 | recv_loop(N-1, S, passive); 49 | recv_loop(N, S, active) -> 50 | receive 51 | {zmq, S, _Msg} -> 52 | ok 53 | end, 54 | recv_loop(N-1, S, active). 55 | 56 | send_loop(0, _, _) -> 57 | ok; 58 | send_loop(N, S, M) -> 59 | erlzmq:send(S, M), 60 | send_loop(N-1, S, M). 61 | 62 | 63 | -------------------------------------------------------------------------------- /perf/local_lat.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | 4 | main([BindTo,MessageSizeStr,RoundtripCountStr]) -> 5 | {MessageSize, _} = string:to_integer(MessageSizeStr), 6 | {RoundtripCount, _} = string:to_integer(RoundtripCountStr), 7 | {ok, Context} = erlzmq:context(), 8 | {ok, Socket} = erlzmq:socket(Context, [rep, {active, false}]), 9 | ok = erlzmq:bind(Socket, BindTo), 10 | Msg = list_to_binary(lists:duplicate(MessageSize, 0)), 11 | ok = erlzmq_perf:local_lat_loop(RoundtripCount, Socket, Msg), 12 | erlzmq:close(Socket), 13 | erlzmq:term(Context). 14 | 15 | -------------------------------------------------------------------------------- /perf/local_lat_active.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | 4 | main([BindTo,MessageSizeStr,RoundtripCountStr]) -> 5 | {MessageSize, _} = string:to_integer(MessageSizeStr), 6 | {RoundtripCount, _} = string:to_integer(RoundtripCountStr), 7 | {ok, Context} = erlzmq:context(), 8 | {ok, Socket} = erlzmq:socket(Context, [rep, {active, true}]), 9 | ok = erlzmq:bind(Socket, BindTo), 10 | Msg = list_to_binary(lists:duplicate(MessageSize, 0)), 11 | ok = erlzmq_perf:local_lat_loop(RoundtripCount, Socket, Msg, active), 12 | erlzmq:close(Socket), 13 | erlzmq:term(Context). 14 | 15 | -------------------------------------------------------------------------------- /perf/local_thr.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | %-mode(compile). 4 | 5 | main([BindTo,MessageSizeStr,MessageCountStr]) -> 6 | {MessageSize, _} = string:to_integer(MessageSizeStr), 7 | {MessageCount, _} = string:to_integer(MessageCountStr), 8 | {ok, Context} = erlzmq:context(), 9 | {ok, Socket} = erlzmq:socket(Context, [sub, {active, false}]), 10 | ok = erlzmq:setsockopt(Socket,subscribe, <<>>), 11 | ok = erlzmq:bind(Socket, BindTo), 12 | erlzmq:recv(Socket), 13 | Start = now(), 14 | erlzmq_perf:recv_loop(MessageCount-1, Socket), 15 | Elapsed = timer:now_diff(now(), Start), 16 | 17 | Throughput = MessageCount / Elapsed * 1000000, 18 | Megabits = Throughput * MessageSize * 8 / 1000000, 19 | 20 | io:format("message size: ~p [B]~n" 21 | "message count: ~p~n" 22 | "mean throughput: ~p [msg/s]~n" 23 | "mean throughput: ~p [Mb/s]~n", 24 | [MessageSize, MessageCount, Throughput, Megabits]), 25 | 26 | erlzmq:close(Socket), 27 | erlzmq:term(Context). 28 | 29 | -------------------------------------------------------------------------------- /perf/local_thr_active.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | %-mode(compile). 4 | 5 | main([BindTo,MessageSizeStr,MessageCountStr]) -> 6 | {MessageSize, _} = string:to_integer(MessageSizeStr), 7 | {MessageCount, _} = string:to_integer(MessageCountStr), 8 | {ok, Context} = erlzmq:context(), 9 | {ok, Socket} = erlzmq:socket(Context, [sub, {active, true}]), 10 | ok = erlzmq:setsockopt(Socket,subscribe, <<>>), 11 | ok = erlzmq:bind(Socket, BindTo), 12 | receive 13 | _ -> 14 | ok 15 | end, 16 | Start = now(), 17 | erlzmq_perf:recv_loop(MessageCount-1, Socket, active), 18 | Elapsed = timer:now_diff(now(), Start), 19 | 20 | Throughput = MessageCount / Elapsed * 1000000, 21 | Megabits = Throughput * MessageSize * 8 / 1000000, 22 | 23 | io:format("message size: ~p [B]~n" 24 | "message count: ~p~n" 25 | "mean throughput: ~p [msg/s]~n" 26 | "mean throughput: ~p [Mb/s]~n", 27 | [MessageSize, MessageCount, Throughput, Megabits]), 28 | 29 | erlzmq:close(Socket), 30 | erlzmq:term(Context). 31 | 32 | -------------------------------------------------------------------------------- /perf/perfgraphs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | import subprocess 3 | import matplotlib.pyplot as plt 4 | 5 | def do_test(localcmd, remotecmd): 6 | local = subprocess.Popen(localcmd.split(), stdout=subprocess.PIPE) 7 | remote = subprocess.Popen(remotecmd.split(), stdout=subprocess.PIPE, 8 | close_fds=True) 9 | local.wait() 10 | remote.wait() 11 | return (local.stdout.read(), remote.stdout.read()) 12 | 13 | def thr_run(iterations, msgsize=1, msgcount=100000, active=False): 14 | localcmd = "escript perf/local_thr"+(active and "_active" or "")+".erl" \ 15 | " tcp://lo:1234 "+str(msgsize)+" "+str(msgcount) 16 | remotecmd = "escript perf/remote_thr.erl" \ 17 | " tcp://localhost:1234 "+str(msgsize)+" "+str(msgcount) 18 | results = [] 19 | for i in xrange(iterations): 20 | out = do_test(localcmd, remotecmd)[0] 21 | results.append(float(out.split('\n')[2].split(' ')[2])) 22 | return results 23 | 24 | def lat_run(iterations, msgsize=1, msgcount=100000, active=False): 25 | localcmd = "escript perf/local_lat"+(active and "_active" or "")+".erl" \ 26 | " tcp://lo:1234 "+str(msgsize)+" "+str(msgcount) 27 | remotecmd = "escript perf/remote_lat"+(active and "_active" or "")+".erl" \ 28 | " tcp://localhost:1234 "+str(msgsize)+" "+str(msgcount) 29 | results = [] 30 | for i in xrange(iterations): 31 | out = do_test(localcmd, remotecmd)[1] 32 | results.append(float(out.split('\n')[2].split(' ')[2])) 33 | return results 34 | 35 | def average(l): 36 | return [sum(l)/float(len(l))]*len(l) 37 | 38 | class TestRun(object): 39 | def __init__(self, iterations, msgsize, msgcount): 40 | self.iterations = iterations 41 | self.msgsize = msgsize 42 | self.msgcount = msgcount 43 | 44 | def plot_all(self): 45 | self.plot_thr() 46 | self.plot_lat() 47 | 48 | def title(self): 49 | return "%d messages in of %d byte(s)" % \ 50 | (self.msgcount, self.msgsize) 51 | 52 | def plot_thr(self): 53 | results = thr_run(self.iterations, self.msgsize, 54 | self.msgcount, False) 55 | results_active = thr_run(self.iterations, self.msgsize, 56 | self.msgcount, True) 57 | plt.clf() 58 | plt.title("Throughput: %s" % self.title()) 59 | plt.ylabel("Msg/s") 60 | plt.plot(results, 'bo-', label="passive") 61 | plt.plot(average(results), 'b-', label="passive average", 62 | linewidth=2) 63 | plt.plot(results_active, 'ro-', label="active") 64 | plt.plot(average(results_active), 'r-', label="active average", 65 | linewidth=2) 66 | plt.legend() 67 | plt.savefig("thr_%d_%d_%d.png" % (self.iterations, self.msgsize, 68 | self.msgcount)) 69 | 70 | def plot_lat(self): 71 | results = lat_run(self.iterations, self.msgsize, 72 | self.msgcount, False) 73 | results_active = lat_run(self.iterations, self.msgsize, 74 | self.msgcount, True) 75 | plt.clf() 76 | plt.title("Latency: %s" % self.title()) 77 | plt.ylabel("Average latency (us)") 78 | plt.plot(results, 'bo-', label="passive") 79 | plt.plot(average(results), 'b-', label="passive average", 80 | linewidth=2) 81 | plt.plot(results_active, 'ro-', label="active") 82 | plt.plot(average(results_active), 'r-', label="active average", 83 | linewidth=2) 84 | plt.legend() 85 | plt.savefig("lat_%d_%d_%d.png" % (self.iterations, self.msgsize, 86 | self.msgcount)) 87 | 88 | if __name__ == "__main__": 89 | TestRun(100, 1, 100000).plot_thr() 90 | TestRun(50, 1024, 100000).plot_thr() 91 | TestRun(40, 1, 10000).plot_lat() 92 | TestRun(20, 1024, 10000).plot_lat() 93 | 94 | -------------------------------------------------------------------------------- /perf/remote_lat.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | 4 | main([ConnectTo,MessageSizeStr,RoundtripCountStr]) -> 5 | {MessageSize, _} = string:to_integer(MessageSizeStr), 6 | {RoundtripCount, _} = string:to_integer(RoundtripCountStr), 7 | {ok, Context} = erlzmq:context(), 8 | {ok, Socket} = erlzmq:socket(Context, [req, {active, false}]), 9 | ok = erlzmq:connect(Socket, ConnectTo), 10 | Msg = list_to_binary(lists:duplicate(MessageSize, 0)), 11 | 12 | Start = now(), 13 | erlzmq_perf:remote_lat_loop(RoundtripCount, Socket, Msg), 14 | Elapsed = timer:now_diff(now(), Start), 15 | 16 | Latency = Elapsed / (RoundtripCount * 2), 17 | 18 | io:format("message size: ~p [B]~n" 19 | "roundtrip count: ~p~n" 20 | "average latency: ~p [us]~n", 21 | [MessageSize, RoundtripCount, Latency]), 22 | erlzmq:close(Socket), 23 | erlzmq:term(Context). 24 | 25 | -------------------------------------------------------------------------------- /perf/remote_lat_active.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | 4 | main([ConnectTo,MessageSizeStr,RoundtripCountStr]) -> 5 | {MessageSize, _} = string:to_integer(MessageSizeStr), 6 | {RoundtripCount, _} = string:to_integer(RoundtripCountStr), 7 | {ok, Context} = erlzmq:context(), 8 | {ok, Socket} = erlzmq:socket(Context, [req, {active, true}]), 9 | ok = erlzmq:connect(Socket, ConnectTo), 10 | Msg = list_to_binary(lists:duplicate(MessageSize, 0)), 11 | 12 | Start = now(), 13 | erlzmq_perf:remote_lat_loop(RoundtripCount, Socket, Msg, active), 14 | Elapsed = timer:now_diff(now(), Start), 15 | 16 | Latency = Elapsed / (RoundtripCount * 2), 17 | 18 | io:format("message size: ~p [B]~n" 19 | "roundtrip count: ~p~n" 20 | "average latency: ~p [us]~n", 21 | [MessageSize, RoundtripCount, Latency]), 22 | erlzmq:close(Socket), 23 | erlzmq:term(Context). 24 | 25 | -------------------------------------------------------------------------------- /perf/remote_thr.erl: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env escript 2 | %%! -smp enable -pa ebin -pa perf 3 | 4 | main([ConnectTo,MessageSizeStr,MessageCountStr]) -> 5 | {MessageSize, _} = string:to_integer(MessageSizeStr), 6 | {MessageCount, _} = string:to_integer(MessageCountStr), 7 | {ok, Context} = erlzmq:context(1), 8 | {ok, Socket} = erlzmq:socket(Context, [pub, {active, false}]), 9 | erlzmq:connect(Socket, ConnectTo), 10 | Msg = list_to_binary(lists:duplicate(MessageSize, 0)), 11 | erlzmq_perf:send_loop(MessageCount, Socket, Msg), 12 | erlzmq:close(Socket), 13 | erlzmq:term(Context). 14 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info, warnings_as_errors]}. 2 | 3 | {port_env, 4 | [{"DRV_LDFLAGS","deps/zeromq3/src/.libs/libzmq.a -shared $ERL_LDFLAGS -lstdc++"}, 5 | {"darwin", "DRV_LDFLAGS", "deps/zeromq3/src/.libs/libzmq.a -bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, 6 | {"DRV_CFLAGS","-Ic_src -Ideps/zeromq3/include -g -Wall -fPIC $ERL_CFLAGS"}]}. 7 | 8 | {port_specs, [{"priv/erlzmq_drv.so", ["c_src/*.c"]}]}. 9 | 10 | {pre_hooks,[{compile,"make -C c_src"}, 11 | {clean, "make -C c_src clean"}]}. 12 | -------------------------------------------------------------------------------- /src/erlzmq.app.src: -------------------------------------------------------------------------------- 1 | {application, erlzmq, 2 | [ 3 | {description, "Erlang ZeroMQ Driver"}, 4 | {vsn, "3.0"}, 5 | {modules, [erlzmq, erlzmq_nif]}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib 10 | ]}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/erlzmq.erl: -------------------------------------------------------------------------------- 1 | %% -*- coding:utf-8;Mode:erlang;tab-width:4;c-basic-offset:4;indent-tabs-mode:nil -*- 2 | %% ex: set softtabstop=4 tabstop=4 shiftwidth=4 expandtab fileencoding=utf-8: 3 | %% 4 | %% Copyright (c) 2011 Yurii Rashkovskii, Evax Software and Michael Truog 5 | %% 6 | %% Permission is hereby granted, free of charge, to any person obtaining a copy 7 | %% of this software and associated documentation files (the "Software"), to deal 8 | %% in the Software without restriction, including without limitation the rights 9 | %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | %% copies of the Software, and to permit persons to whom the Software is 11 | %% furnished to do so, subject to the following conditions: 12 | %% 13 | %% The above copyright notice and this permission notice shall be included in 14 | %% all copies or substantial portions of the Software. 15 | %% 16 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | %% THE SOFTWARE. 23 | 24 | -module(erlzmq). 25 | %% @headerfile "erlzmq.hrl" 26 | -include_lib("erlzmq.hrl"). 27 | -export([context/0, 28 | context/1, 29 | socket/2, 30 | bind/2, 31 | connect/2, 32 | send/2, 33 | send/3, 34 | sendmsg/2, 35 | sendmsg/3, 36 | recv/1, 37 | recv/2, 38 | recvmsg/1, 39 | recvmsg/2, 40 | setsockopt/3, 41 | getsockopt/2, 42 | close/1, 43 | close/2, 44 | term/1, 45 | term/2, 46 | version/0]). 47 | -export_type([erlzmq_socket/0, erlzmq_context/0]). 48 | 49 | %% @equiv context(1) 50 | -spec context() -> 51 | {ok, erlzmq_context()} | 52 | erlzmq_error(). 53 | context() -> 54 | context(1). 55 | 56 | %% @doc Create a new erlzmq context with the specified number of io threads. 57 | %%
58 | %% If the context can be created an 'ok' tuple containing an 59 | %% {@type erlzmq_context()} handle to the created context is returned; 60 | %% if not, it returns an 'error' tuple with an {@type erlzmq_type_error()} 61 | %% describing the error. 62 | %%
63 | %% The context must be later cleaned up calling {@link erlzmq:term/1. term/1} 64 | %%
65 | %% For more information see 66 | %% zmq_init 67 | %% @end 68 | -spec context(Threads :: pos_integer()) -> 69 | {ok, erlzmq_context()} | 70 | erlzmq_error(). 71 | context(Threads) when is_integer(Threads) -> 72 | erlzmq_nif:context(Threads). 73 | 74 | 75 | %% @doc Create a socket. 76 | %%
77 | %% This functions creates a socket of the given 78 | %% {@link erlzmq_socket_type(). type}, optionally setting it to active mode, 79 | %% and associates it with the given {@link erlzmq_context(). context}. 80 | %%
81 | %% If the socket can be created an 'ok' tuple containing a 82 | %% {@type erlzmq_socket()} handle to the created socket is returned; 83 | %% if not, it returns an {@type erlzmq_error()} describing the error. 84 | %%
85 | %% In line with Erlang's socket paradigm, a socket can be either active or 86 | %% passive. Passive sockets tend to have lower latency and have a higher 87 | %% throughput for small message sizes. Active sockets on the contrary give 88 | %% the highest throughput for messages above 32k. A benchmarking tool is 89 | %% included in the source distribution.
90 | %% For more information see 91 | %% zmq_socket. 92 | %% @end 93 | -spec socket(Context :: erlzmq_context(), 94 | Type :: erlzmq_socket_type() | 95 | list(erlzmq_socket_type() | 96 | {active, boolean()} | 97 | {active_pid, pid()})) -> 98 | {ok, erlzmq_socket()} | 99 | erlzmq_error(). 100 | socket(Context, Type) when is_atom(Type) -> 101 | socket(Context, [Type]); 102 | socket(Context, [H | T]) when is_atom(H) -> 103 | case T of 104 | [] -> 105 | % active is false by default 106 | % (to avoid latency on small messages (messages < 32KB)) 107 | socket(Context, H, {active, false}); 108 | [Active] -> 109 | socket(Context, H, Active) 110 | end; 111 | socket(Context, [H | [Type]]) when is_tuple(H) -> 112 | socket(Context, Type, H). 113 | 114 | -spec socket(Context :: erlzmq_context(), 115 | Type :: erlzmq_socket_type(), 116 | {active, boolean()} | {active_pid, pid()}) -> 117 | {ok, erlzmq_socket()} | 118 | erlzmq_error(). 119 | socket(Context, Type, {active, true}) -> 120 | true = (Type =/= pub) and (Type =/= push) and (Type =/= xpub), 121 | erlzmq_nif:socket(Context, socket_type(Type), 1, self()); 122 | socket(Context, Type, {active_pid, Pid}) 123 | when is_pid(Pid), node(Pid) =:= node() -> 124 | true = (Type =/= pub) and (Type =/= push) and (Type =/= xpub), 125 | erlzmq_nif:socket(Context, socket_type(Type), 1, Pid); 126 | socket(Context, Type, {active, false}) -> 127 | erlzmq_nif:socket(Context, socket_type(Type), 0, self()). 128 | 129 | 130 | %% @doc Accept connections on a socket. 131 | %%
132 | %% For more information see 133 | %% zmq_bind. 134 | %% @end 135 | -spec bind(Socket :: erlzmq_socket(), 136 | Endpoint :: erlzmq_endpoint()) -> 137 | ok | 138 | erlzmq_error(). 139 | bind({I, Socket}, Endpoint) 140 | when is_integer(I), is_list(Endpoint) -> 141 | erlzmq_nif:bind(Socket, Endpoint); 142 | bind({I, Socket}, Endpoint) 143 | when is_integer(I), is_binary(Endpoint) -> 144 | bind({I, Socket}, binary_to_list(Endpoint)). 145 | 146 | %% @doc Connect a socket. 147 | %%
148 | %% For more information see 149 | %% zmq_connect. 150 | %% @end 151 | -spec connect(Socket :: erlzmq_socket(), 152 | Endpoint :: erlzmq_endpoint()) -> 153 | ok | 154 | erlzmq_error(). 155 | connect({I, Socket}, Endpoint) 156 | when is_integer(I), is_list(Endpoint) -> 157 | erlzmq_nif:connect(Socket, Endpoint); 158 | connect({I, Socket}, Endpoint) 159 | when is_integer(I), is_binary(Endpoint) -> 160 | connect({I, Socket}, binary_to_list(Endpoint)). 161 | 162 | %% @equiv send(Socket, Msg, []) 163 | -spec send(erlzmq_socket(), 164 | Binary :: binary()) -> 165 | ok | 166 | erlzmq_error(). 167 | send(Socket, Binary) when is_binary(Binary) -> 168 | send(Socket, Binary, []). 169 | 170 | %% @doc Send a message on a socket. 171 | %%
172 | %% For more information see 173 | %% zmq_send. 174 | %% @end 175 | -spec send(erlzmq_socket(), 176 | Binary :: binary(), 177 | Flags :: erlzmq_send_recv_flags()) -> 178 | ok | 179 | erlzmq_error(). 180 | send({I, Socket}, Binary, Flags) 181 | when is_integer(I), is_binary(Binary), is_list(Flags) -> 182 | case erlzmq_nif:send(Socket, Binary, sendrecv_flags(Flags)) of 183 | Ref when is_reference(Ref) -> 184 | receive 185 | {Ref, ok} -> 186 | ok; 187 | {Ref, {error, _} = Error} -> 188 | Error 189 | after case erlzmq_nif:getsockopt(Socket,?'ZMQ_SNDTIMEO') of 190 | {ok, -1} -> 191 | infinity; 192 | {ok, Else} -> 193 | Else 194 | end -> 195 | {error, eagain} 196 | end; 197 | Result -> 198 | Result 199 | end. 200 | 201 | %% @equiv send(Socket, Msg, []) 202 | %% @doc This function exists for zeromq api compatibility and doesn't 203 | %% actually provide any different functionality then what you get with 204 | %% the {@link erlzmq:send/2} function. In fact this function just 205 | %% calls that function. So there is a slight bit of additional 206 | %% overhead as well. 207 | -spec sendmsg(erlzmq_socket(), 208 | Binary :: binary()) -> 209 | ok | 210 | erlzmq_error(). 211 | sendmsg(Socket, Binary) when is_binary(Binary) -> 212 | send(Socket, Binary, []). 213 | 214 | %% @equiv send(Socket, Msg, Flags) 215 | %% @doc This function exists for zeromq api compatibility and doesn't 216 | %% actually provide any different functionality then what you get with 217 | %% the {@link erlzmq:send/3} function. In fact this function just 218 | %% calls that function. So there is a slight bit of additional 219 | %% overhead as well. 220 | -spec sendmsg(erlzmq_socket(), 221 | Binary :: binary(), 222 | Flags :: erlzmq_send_recv_flags()) -> 223 | ok | 224 | erlzmq_error(). 225 | sendmsg(Socket, Binary, Flags) -> 226 | send(Socket, Binary, Flags). 227 | 228 | 229 | %% @equiv recv(Socket, 0) 230 | -spec recv(Socket :: erlzmq_socket()) -> 231 | {ok, erlzmq_data()} | 232 | erlzmq_error(). 233 | recv(Socket) -> 234 | recv(Socket, []). 235 | 236 | %% @doc Receive a message from a socket. 237 | %%
238 | %% For more information see 239 | %% zmq_recv. 240 | %% @end 241 | -spec recv(Socket :: erlzmq_socket(), 242 | Flags :: erlzmq_send_recv_flags()) -> 243 | {ok, erlzmq_data()} | 244 | erlzmq_error(). 245 | recv({I, Socket}, Flags) 246 | when is_integer(I), is_list(Flags) -> 247 | case erlzmq_nif:recv(Socket, sendrecv_flags(Flags)) of 248 | Ref when is_reference(Ref) -> 249 | receive 250 | {Ref, {error, _} = Error} -> 251 | Error; 252 | {Ref, Result} -> 253 | {ok, Result} 254 | after case erlzmq_nif:getsockopt(Socket,?'ZMQ_RCVTIMEO') of 255 | {ok, -1} -> 256 | infinity; 257 | {ok, Else} -> 258 | Else 259 | end -> 260 | {error, eagain} 261 | end; 262 | Result -> 263 | Result 264 | end. 265 | 266 | %% @equiv recv(Socket, 0) 267 | %% @doc This function exists for zeromq api compatibility and doesn't 268 | %% actually provide any different functionality then what you get with 269 | %% the {@link erlzmq:recv/3} function. In fact this function just 270 | %% calls that function. So there is a slight bit of additional 271 | %% overhead as well. 272 | -spec recvmsg(Socket :: erlzmq_socket()) -> 273 | {ok, erlzmq_data()} | 274 | erlzmq_error(). 275 | recvmsg(Socket) -> 276 | recv(Socket, []). 277 | 278 | %% @equiv recv(Socket, Flags) 279 | %% @doc This function exists for zeromq api compatibility and doesn't 280 | %% actually provide any different functionality then what you get with 281 | %% the {@link erlzmq:recv/3} function. In fact this function just 282 | %% calls that function. So there is a slight bit of additional 283 | %% overhead as well. 284 | -spec recvmsg(Socket :: erlzmq_socket(), 285 | Flags :: erlzmq_send_recv_flags()) -> 286 | {ok, erlzmq_data()} | 287 | erlzmq_error(). 288 | recvmsg(Socket, Flags) -> 289 | recv(Socket, Flags). 290 | 291 | %% @doc Set an {@link erlzmq_sockopt(). option} associated with a socket. 292 | %%
293 | %% For more information see 294 | %% zmq_setsockopt. 295 | %% @end 296 | -spec setsockopt(erlzmq_socket(), 297 | Name :: erlzmq_sockopt(), 298 | erlzmq_sockopt_value() | binary()) -> 299 | ok | 300 | erlzmq_error(). 301 | setsockopt(Socket, Name, Value) when is_list(Value) -> 302 | setsockopt(Socket, Name, erlang:list_to_binary(Value)); 303 | setsockopt({I, Socket}, Name, Value) when is_integer(I), is_atom(Name) -> 304 | erlzmq_nif:setsockopt(Socket, option_name(Name), Value). 305 | 306 | %% @doc Get an {@link erlzmq_sockopt(). option} associated with a socket. 307 | %%
308 | %% For more information see 309 | %% zmq_getsockopt. 310 | %% @end 311 | -spec getsockopt(Socket :: erlzmq_socket(), 312 | Name :: erlzmq_sockopt()) -> 313 | {ok, erlzmq_sockopt_value()} | 314 | erlzmq_error(). 315 | getsockopt({I, Socket}, Name) when is_integer(I), is_atom(Name) -> 316 | erlzmq_nif:getsockopt(Socket, option_name(Name)). 317 | 318 | %% @equiv close(Socket, infinity) 319 | -spec close(Socket :: erlzmq_socket()) -> 320 | ok | 321 | erlzmq_error(). 322 | close(Socket) -> 323 | close(Socket, infinity). 324 | 325 | %% @doc Close the given socket. 326 | %%
327 | %% For more information see 328 | %% zmq_close. 329 | %% @end 330 | -spec close(Socket :: erlzmq_socket(), 331 | Timeout :: timeout()) -> 332 | ok | 333 | erlzmq_error(). 334 | close({I, Socket}, Timeout) when is_integer(I) -> 335 | case erlzmq_nif:close(Socket) of 336 | Ref when is_reference(Ref) -> 337 | receive 338 | {Ref, Result} -> 339 | Result 340 | after 341 | Timeout -> 342 | {error, {timeout, Ref}} 343 | end; 344 | Result -> 345 | Result 346 | end. 347 | 348 | %% @equiv term(Context, infinity) 349 | -spec term(Context :: erlzmq_context()) -> 350 | ok | 351 | erlzmq_error(). 352 | term(Context) -> 353 | term(Context, infinity). 354 | 355 | %% @doc Terminate the given context waiting up to Timeout ms. 356 | %%
357 | %% This function should be called after all sockets associated with 358 | %% the given context have been closed.
359 | %% If not it will block the given Timeout amount of time. 360 | %% For more information see 361 | %% zmq_term. 362 | %% @end 363 | -spec term(Context :: erlzmq_context(), 364 | Timeout :: timeout()) -> 365 | ok | 366 | erlzmq_error() | 367 | {error, {timeout, reference()}}. 368 | 369 | term(Context, Timeout) -> 370 | case erlzmq_nif:term(Context) of 371 | Ref when is_reference(Ref) -> 372 | receive 373 | {Ref, Result} -> 374 | Result 375 | after 376 | Timeout -> 377 | {error, {timeout, Ref}} 378 | end; 379 | Result -> 380 | Result 381 | end. 382 | 383 | %% @doc Returns the 0MQ library version. 384 | %% @end 385 | -spec version() -> {integer(), integer(), integer()}. 386 | 387 | version() -> erlzmq_nif:version(). 388 | 389 | %% Private 390 | 391 | -spec socket_type(Type :: erlzmq_socket_type()) -> 392 | integer(). 393 | 394 | socket_type(pair) -> 395 | ?'ZMQ_PAIR'; 396 | socket_type(pub) -> 397 | ?'ZMQ_PUB'; 398 | socket_type(sub) -> 399 | ?'ZMQ_SUB'; 400 | socket_type(req) -> 401 | ?'ZMQ_REQ'; 402 | socket_type(rep) -> 403 | ?'ZMQ_REP'; 404 | socket_type(dealer) -> 405 | ?'ZMQ_DEALER'; 406 | socket_type(xreq) -> 407 | ?'ZMQ_XREQ'; 408 | socket_type(router) -> 409 | ?'ZMQ_ROUTER'; 410 | socket_type(xrep) -> 411 | ?'ZMQ_XREP'; 412 | socket_type(pull) -> 413 | ?'ZMQ_PULL'; 414 | socket_type(push) -> 415 | ?'ZMQ_PUSH'; 416 | socket_type(xpub) -> 417 | ?'ZMQ_XPUB'; 418 | socket_type(xsub) -> 419 | ?'ZMQ_XSUB'. 420 | 421 | -spec sendrecv_flags(Flags :: erlzmq_send_recv_flags()) -> 422 | integer(). 423 | 424 | sendrecv_flags([]) -> 425 | 0; 426 | sendrecv_flags([dontwait|Rest]) -> 427 | ?'ZMQ_DONTWAIT' bor sendrecv_flags(Rest); 428 | sendrecv_flags([sndmore|Rest]) -> 429 | ?'ZMQ_SNDMORE' bor sendrecv_flags(Rest). 430 | 431 | -spec option_name(Name :: erlzmq_sockopt()) -> 432 | integer(). 433 | 434 | option_name(affinity) -> 435 | ?'ZMQ_AFFINITY'; 436 | option_name(identity) -> 437 | ?'ZMQ_IDENTITY'; 438 | option_name(subscribe) -> 439 | ?'ZMQ_SUBSCRIBE'; 440 | option_name(unsubscribe) -> 441 | ?'ZMQ_UNSUBSCRIBE'; 442 | option_name(rate) -> 443 | ?'ZMQ_RATE'; 444 | option_name(recovery_ivl) -> 445 | ?'ZMQ_RECOVERY_IVL'; 446 | option_name(sndbuf) -> 447 | ?'ZMQ_SNDBUF'; 448 | option_name(rcvbuf) -> 449 | ?'ZMQ_RCVBUF'; 450 | option_name(rcvmore) -> 451 | ?'ZMQ_RCVMORE'; 452 | option_name(fd) -> 453 | ?'ZMQ_FD'; 454 | option_name(events) -> 455 | ?'ZMQ_EVENTS'; 456 | option_name(linger) -> 457 | ?'ZMQ_LINGER'; 458 | option_name(reconnect_ivl) -> 459 | ?'ZMQ_RECONNECT_IVL'; 460 | option_name(backlog) -> 461 | ?'ZMQ_BACKLOG'; 462 | option_name(reconnect_ivl_max) -> 463 | ?'ZMQ_RECONNECT_IVL_MAX'; 464 | option_name(maxmsgsize) -> 465 | ?'ZMQ_MAXMSGSIZE'; 466 | option_name(sndhwm) -> 467 | ?'ZMQ_SNDHWM'; 468 | option_name(rcvhwm) -> 469 | ?'ZMQ_RCVHWM'; 470 | option_name(multicast_hops) -> 471 | ?'ZMQ_MULTICAST_HOPS'; 472 | option_name(rcvtimeo) -> 473 | ?'ZMQ_RCVTIMEO'; 474 | option_name(sndtimeo) -> 475 | ?'ZMQ_SNDTIMEO'; 476 | option_name(ipv4only) -> 477 | ?'ZMQ_IPV4ONLY'. 478 | -------------------------------------------------------------------------------- /src/erlzmq_nif.erl: -------------------------------------------------------------------------------- 1 | %% @hidden 2 | -module(erlzmq_nif). 3 | 4 | -export([context/1, 5 | socket/4, 6 | bind/2, 7 | connect/2, 8 | send/3, 9 | recv/2, 10 | setsockopt/3, 11 | getsockopt/2, 12 | close/1, 13 | term/1, 14 | version/0]). 15 | 16 | -on_load(init/0). 17 | 18 | -ifdef(TEST). 19 | -include_lib("eunit/include/eunit.hrl"). 20 | -endif. 21 | 22 | init() -> 23 | case code:priv_dir(erlzmq) of 24 | Path when is_list(Path) -> 25 | erlang:load_nif(filename:join([Path, "erlzmq_drv"]), []); 26 | {error, bad_name} -> 27 | case code:which(erlzmq_nif) of 28 | Filename when is_list(Filename) -> 29 | erlang:load_nif(filename:join([filename:dirname(Filename), 30 | "..","priv", 31 | "erlzmq_drv"]), []); 32 | Reason when is_atom(Reason) -> 33 | {error, Reason} 34 | end 35 | end. 36 | 37 | context(_Threads) -> 38 | erlang:nif_error(not_loaded). 39 | 40 | socket(_Context, _Type, _Active, _ActivePid) -> 41 | erlang:nif_error(not_loaded). 42 | 43 | bind(_Socket, _Endpoint) -> 44 | erlang:nif_error(not_loaded). 45 | 46 | connect(_Socket, _Endpoint) -> 47 | erlang:nif_error(not_loaded). 48 | 49 | send(_Socket, _Binary, _Flags) -> 50 | erlang:nif_error(not_loaded). 51 | 52 | recv(_Socket, _Flags) -> 53 | erlang:nif_error(not_loaded). 54 | 55 | setsockopt(_Socket, _OptionName, _OptionValue) -> 56 | erlang:nif_error(not_loaded). 57 | 58 | getsockopt(_Socket, _OptionName) -> 59 | erlang:nif_error(not_loaded). 60 | 61 | close(_Socket) -> 62 | erlang:nif_error(not_loaded). 63 | 64 | term(_Context) -> 65 | erlang:nif_error(not_loaded). 66 | 67 | version() -> 68 | erlang:nif_error(not_loaded). 69 | -------------------------------------------------------------------------------- /test/erlzmq_test.erl: -------------------------------------------------------------------------------- 1 | -module(erlzmq_test). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -export([worker/2]). 4 | 5 | % provides some context for failures only viewable within the C code 6 | %-define(PRINT_DEBUG, true). 7 | 8 | -ifdef(PRINT_DEBUG). 9 | % use stderr while bypassing the io server to avoid buffering 10 | -define(PRINT_START, 11 | PRINT_PORT = open_port({fd, 0, 2}, [out, {line, 256}]), 12 | port_command(PRINT_PORT, 13 | io_lib:format("~w:~w start~n", [?MODULE, ?LINE]))). 14 | -define(PRINT_CHECK(ANY), 15 | port_command(PRINT_PORT, 16 | io_lib:format("~w:~w ~p~n", [?MODULE, ?LINE, ANY]))). 17 | -define(PRINT_END, 18 | port_command(PRINT_PORT, 19 | io_lib:format("~w:~w end~n", [?MODULE, ?LINE])), 20 | port_close(PRINT_PORT), 21 | ok). 22 | -else. 23 | -define(PRINT_START, ok). 24 | -define(PRINT_CHECK(_), ok). 25 | -define(PRINT_END, ok). 26 | -endif. 27 | 28 | hwm_test() -> 29 | ?PRINT_START, 30 | ?PRINT_CHECK(lists:flatten( 31 | io_lib:format("executing as os pid ~s", [os:getpid()]))), 32 | {ok, C} = erlzmq:context(), 33 | {ok, S1} = erlzmq:socket(C, [pull, {active, false}]), 34 | {ok, S2} = erlzmq:socket(C, [push, {active, false}]), 35 | 36 | ok = erlzmq:setsockopt(S1, rcvhwm, 2), 37 | ok = erlzmq:setsockopt(S2, sndhwm, 2), 38 | 39 | 40 | ok = erlzmq:bind(S1, "inproc://a"), 41 | ok = erlzmq:connect(S2, "inproc://a"), 42 | 43 | ok = hwm_loop(10, S2), 44 | 45 | ?assertMatch({ok, <<"test">>}, erlzmq:recv(S1)), 46 | ?assertMatch({ok, <<"test">>}, erlzmq:recv(S1)), 47 | ?assertMatch({ok, <<"test">>}, erlzmq:recv(S1)), 48 | ?assertMatch({ok, <<"test">>}, erlzmq:recv(S1)), 49 | 50 | ?assertMatch(ok, erlzmq:send(S2, <<"test">>)), 51 | 52 | ?assertMatch({ok, <<"test">>}, erlzmq:recv(S1)), 53 | 54 | ok = erlzmq:close(S1), 55 | ok = erlzmq:close(S2), 56 | ok = erlzmq:term(C), 57 | ?PRINT_END. 58 | 59 | hwm_loop(0, _S) -> 60 | ok; 61 | hwm_loop(N, S) when N > 6 -> 62 | ?assertMatch(ok, erlzmq:send(S, <<"test">>, [dontwait])), 63 | hwm_loop(N-1, S); 64 | hwm_loop(N, S) -> 65 | ?assertMatch({error, _} ,erlzmq:send(S, <<"test">>, [dontwait])), 66 | hwm_loop(N-1, S). 67 | 68 | invalid_rep_test() -> 69 | ?PRINT_START, 70 | {ok, Ctx} = erlzmq:context(), 71 | 72 | {ok, XrepSocket} = erlzmq:socket(Ctx, [xrep, {active, false}]), 73 | {ok, ReqSocket} = erlzmq:socket(Ctx, [req, {active, false}]), 74 | 75 | ok = erlzmq:setsockopt(XrepSocket, linger, 0), 76 | ok = erlzmq:setsockopt(ReqSocket, linger, 0), 77 | ok = erlzmq:bind(XrepSocket, "inproc://hi"), 78 | ok = erlzmq:connect(ReqSocket, "inproc://hi"), 79 | 80 | %% Initial request. 81 | ok = erlzmq:send(ReqSocket, <<"r">>), 82 | 83 | %% Receive the request. 84 | {ok, Addr} = erlzmq:recv(XrepSocket), 85 | {ok, Bottom} = erlzmq:recv(XrepSocket), 86 | {ok, _Body} = erlzmq:recv(XrepSocket), 87 | 88 | %% Send invalid reply. 89 | ok = erlzmq:send(XrepSocket, Addr), 90 | 91 | %% Send valid reply. 92 | ok = erlzmq:send(XrepSocket, Addr, [sndmore]), 93 | ok = erlzmq:send(XrepSocket, Bottom, [sndmore]), 94 | ok = erlzmq:send(XrepSocket, <<"b">>), 95 | 96 | %% Check whether we've got the valid reply. 97 | {ok, <<"b">>} = erlzmq:recv(ReqSocket), 98 | 99 | %% Tear down the wiring. 100 | ok = erlzmq:close(XrepSocket), 101 | ok = erlzmq:close(ReqSocket), 102 | ok = erlzmq:term(Ctx), 103 | ?PRINT_END. 104 | 105 | pair_inproc_test() -> 106 | ?PRINT_START, 107 | basic_tests("inproc://tester", pair, pair, active), 108 | basic_tests("inproc://tester", pair, pair, passive), 109 | ?PRINT_END. 110 | 111 | pair_ipc_test() -> 112 | ?PRINT_START, 113 | basic_tests("ipc:///tmp/tester", pair, pair, active), 114 | basic_tests("ipc:///tmp/tester", pair, pair, passive), 115 | ?PRINT_END. 116 | 117 | pair_tcp_test() -> 118 | ?PRINT_START, 119 | basic_tests("tcp://127.0.0.1:5554", pair, pair, active), 120 | basic_tests("tcp://127.0.0.1:5555", pair, pair, passive), 121 | ?PRINT_END. 122 | 123 | reqrep_device_test() -> 124 | ?PRINT_START, 125 | {ok, Ctx} = erlzmq:context(), 126 | 127 | %% Create a req/rep device. 128 | {ok, Xreq} = erlzmq:socket(Ctx, [xreq, {active, false}]), 129 | ok = erlzmq:bind(Xreq, "tcp://127.0.0.1:5560"), 130 | {ok, Xrep} = erlzmq:socket(Ctx, [xrep, {active, false}]), 131 | ok = erlzmq:bind(Xrep, "tcp://127.0.0.1:5561"), 132 | 133 | %% Create a worker. 134 | {ok, Rep} = erlzmq:socket(Ctx, [rep, {active, false}]), 135 | ok= erlzmq:connect(Rep, "tcp://127.0.0.1:5560"), 136 | 137 | %% Create a client. 138 | {ok, Req} = erlzmq:socket(Ctx, [req, {active, false}]), 139 | ok = erlzmq:connect(Req, "tcp://127.0.0.1:5561"), 140 | 141 | %% Send a request. 142 | ok = erlzmq:send(Req, <<"ABC">>, [sndmore]), 143 | ok = erlzmq:send(Req, <<"DEF">>), 144 | 145 | 146 | %% Pass the request through the device. 147 | lists:foreach(fun(_) -> 148 | {ok, Msg} = erlzmq:recv(Xrep), 149 | {ok, RcvMore}= erlzmq:getsockopt(Xrep, rcvmore), 150 | case RcvMore of 151 | 0 -> 152 | ok = erlzmq:send(Xreq, Msg); 153 | _ -> 154 | ok = erlzmq:send(Xreq, Msg, [sndmore]) 155 | end 156 | end, 157 | lists:seq(1, 4)), 158 | 159 | %% Receive the request. 160 | {ok, Buff0} = erlzmq:recv(Rep), 161 | ?assertMatch(<<"ABC">>, Buff0), 162 | {ok, RcvMore1} = erlzmq:getsockopt(Rep, rcvmore), 163 | ?assert(RcvMore1 > 0), 164 | {ok, Buff1} = erlzmq:recv(Rep), 165 | ?assertMatch(<<"DEF">>, Buff1), 166 | {ok, RcvMore2} = erlzmq:getsockopt(Rep, rcvmore), 167 | ?assertMatch(0, RcvMore2), 168 | 169 | %% Send the reply. 170 | ok = erlzmq:send(Rep, <<"GHI">>, [sndmore]), 171 | ok = erlzmq:send (Rep, <<"JKL">>), 172 | 173 | %% Pass the reply through the device. 174 | lists:foreach(fun(_) -> 175 | {ok, Msg} = erlzmq:recv(Xreq), 176 | {ok,RcvMore3} = erlzmq:getsockopt(Xreq, rcvmore), 177 | case RcvMore3 of 178 | 0 -> 179 | ok = erlzmq:send(Xrep, Msg); 180 | _ -> 181 | ok = erlzmq:send(Xrep, Msg, [sndmore]) 182 | end 183 | end, lists:seq(1, 4)), 184 | 185 | %% Receive the reply. 186 | {ok, Buff2} = erlzmq:recv(Req), 187 | ?assertMatch(<<"GHI">>, Buff2), 188 | {ok, RcvMore4} = erlzmq:getsockopt(Req, rcvmore), 189 | ?assert(RcvMore4 > 0), 190 | {ok, Buff3} = erlzmq:recv(Req), 191 | ?assertMatch(<<"JKL">>, Buff3), 192 | {ok, RcvMore5} = erlzmq:getsockopt(Req, rcvmore), 193 | ?assertMatch(0, RcvMore5), 194 | 195 | %% Clean up. 196 | ok = erlzmq:close(Req), 197 | ok = erlzmq:close(Rep), 198 | ok = erlzmq:close(Xrep), 199 | ok = erlzmq:close(Xreq), 200 | ok = erlzmq:term(Ctx), 201 | ?PRINT_END. 202 | 203 | 204 | reqrep_inproc_test() -> 205 | ?PRINT_START, 206 | basic_tests("inproc://test", req, rep, active), 207 | basic_tests("inproc://test", req, rep, passive), 208 | ?PRINT_END. 209 | 210 | reqrep_ipc_test() -> 211 | ?PRINT_START, 212 | basic_tests("ipc:///tmp/tester", req, rep, active), 213 | basic_tests("ipc:///tmp/tester", req, rep, passive), 214 | ?PRINT_END. 215 | 216 | reqrep_tcp_test() -> 217 | ?PRINT_START, 218 | basic_tests("tcp://127.0.0.1:5556", req, rep, active), 219 | basic_tests("tcp://127.0.0.1:5557", req, rep, passive), 220 | ?PRINT_END. 221 | 222 | 223 | sub_forward_test() -> 224 | ?PRINT_START, 225 | {ok, Ctx} = erlzmq:context(), 226 | 227 | %% First, create an intermediate device. 228 | {ok, Xpub} = erlzmq:socket(Ctx, [xpub, {active, false}]), 229 | 230 | ok = erlzmq:bind(Xpub, "tcp://127.0.0.1:5560"), 231 | 232 | {ok, Xsub} = erlzmq:socket(Ctx, [xsub, {active, false}]), 233 | 234 | ok = erlzmq:bind(Xsub, "tcp://127.0.0.1:5561"), 235 | 236 | %% Create a publisher. 237 | {ok, Pub} = erlzmq:socket(Ctx, [pub, {active, false}]), 238 | 239 | ok = erlzmq:connect(Pub, "tcp://127.0.0.1:5561"), 240 | 241 | %% Create a subscriber. 242 | {ok, Sub} = erlzmq:socket(Ctx, [sub, {active, false}]), 243 | 244 | ok = erlzmq:connect(Sub, "tcp://127.0.0.1:5560"), 245 | 246 | %% Subscribe for all messages. 247 | ok = erlzmq:setsockopt(Sub, subscribe, <<"">>), 248 | 249 | %% Pass the subscription upstream through the device. 250 | {ok, Buff0} = erlzmq:recv(Xpub), 251 | ok = erlzmq:send(Xsub, Buff0), 252 | 253 | %% Wait a bit till the subscription gets to the publisher. 254 | timer:sleep(1000), 255 | 256 | %% Send an empty message. 257 | ok = erlzmq:send(Pub, <<>>), 258 | 259 | %% Pass the message downstream through the device. 260 | {ok, Buff} = erlzmq:recv(Xsub), 261 | 262 | ok = erlzmq:send(Xpub, Buff), 263 | 264 | %% Receive the message in the subscriber. 265 | {ok, Buff} = erlzmq:recv(Sub), 266 | 267 | %% Clean up. 268 | ok = erlzmq:close(Xpub), 269 | ok = erlzmq:close(Xsub), 270 | ok = erlzmq:close(Pub), 271 | ok = erlzmq:close(Sub), 272 | ok = erlzmq:term(Ctx), 273 | ?PRINT_END. 274 | 275 | timeo() -> 276 | ?PRINT_START, 277 | {ok, Ctx} = erlzmq:context(), 278 | %% Create a disconnected socket. 279 | {ok, Sb} = erlzmq:socket(Ctx, [pull, {active, false}]), 280 | ok = erlzmq:bind(Sb, "inproc://timeout_test"), 281 | %% Check whether non-blocking recv returns immediately. 282 | {error, eagain} = erlzmq:recv(Sb, [dontwait]), 283 | %% Check whether recv timeout is honoured. 284 | Timeout0 = 500, 285 | ok = erlzmq:setsockopt(Sb, rcvtimeo, Timeout0), 286 | {Elapsed0, _} = 287 | timer:tc(fun() -> 288 | ?assertMatch({error, eagain}, erlzmq:recv(Sb)) 289 | end), 290 | ?assert(Elapsed0 > 440000 andalso Elapsed0 < 550000), 291 | 292 | %% Check whether connection during the wait doesn't distort the timeout. 293 | Timeout1 = 2000, 294 | ok = erlzmq:setsockopt(Sb, rcvtimeo, Timeout1), 295 | proc_lib:spawn(fun() -> 296 | timer:sleep(1000), 297 | {ok, Sc} = erlzmq:socket(Ctx, [push, {active, false}]), 298 | ok = erlzmq:connect(Sc, "inproc://timeout_test"), 299 | timer:sleep(1000), 300 | ok = erlzmq:close(Sc) 301 | end), 302 | {Elapsed1, _} = timer:tc(fun() -> 303 | ?assertMatch({error, eagain}, erlzmq:recv(Sb)) 304 | end), 305 | ?assert(Elapsed1 > 1900000 andalso Elapsed1 < 2100000), 306 | 307 | %% Check that timeouts don't break normal message transfer. 308 | {ok, Sc} = erlzmq:socket(Ctx, [push, {active, false}]), 309 | ok = erlzmq:setsockopt(Sb, rcvtimeo, Timeout1), 310 | ok = erlzmq:setsockopt(Sb, sndtimeo, Timeout1), 311 | ok = erlzmq:connect(Sc, "inproc://timeout_test"), 312 | 313 | Buff = <<"12345678ABCDEFGH12345678abcdefgh">>, 314 | ok = erlzmq:send(Sc, Buff), 315 | case erlzmq:recv(Sb) of 316 | {ok, Buff} -> 317 | ok; 318 | {error, eagain} -> 319 | timeout 320 | end, 321 | %% Clean-up. 322 | ok = erlzmq:close(Sc), 323 | ok = erlzmq:close(Sb), 324 | ok = erlzmq:term (Ctx), 325 | ok, 326 | ?PRINT_END. 327 | 328 | timeo_test_() -> 329 | % sometimes this test can timeout with the default timeout 330 | {timeout, 10, [ 331 | ?_assert(timeo() =:= ok) 332 | ]}. 333 | 334 | bad_init_test() -> 335 | ?PRINT_START, 336 | ?assertEqual({error, einval}, erlzmq:context(-1)), 337 | ?PRINT_END. 338 | 339 | shutdown_stress_test() -> 340 | ?PRINT_START, 341 | ?assertMatch(ok, shutdown_stress_loop(10)), 342 | ?PRINT_END. 343 | 344 | version_test() -> 345 | ?PRINT_START, 346 | {Major, Minor, Patch} = erlzmq:version(), 347 | ?assert(is_integer(Major) andalso is_integer(Minor) andalso is_integer(Patch)), 348 | ?PRINT_END. 349 | 350 | shutdown_stress_loop(0) -> 351 | ok; 352 | shutdown_stress_loop(N) -> 353 | {ok, C} = erlzmq:context(7), 354 | {ok, S1} = erlzmq:socket(C, [rep, {active, false}]), 355 | ?assertMatch(ok, shutdown_stress_worker_loop(100, C)), 356 | ?assertMatch(ok, join_procs(100)), 357 | ?assertMatch(ok, erlzmq:close(S1)), 358 | ?assertMatch(ok, erlzmq:term(C)), 359 | shutdown_stress_loop(N-1). 360 | 361 | shutdown_no_blocking_test() -> 362 | ?PRINT_START, 363 | {ok, C} = erlzmq:context(), 364 | {ok, S} = erlzmq:socket(C, [pub, {active, false}]), 365 | erlzmq:close(S), 366 | ?assertEqual(ok, erlzmq:term(C, 500)), 367 | ?PRINT_END. 368 | 369 | shutdown_blocking_test() -> 370 | ?PRINT_START, 371 | {ok, C} = erlzmq:context(), 372 | {ok, _S} = erlzmq:socket(C, [pub, {active, false}]), 373 | case erlzmq:term(C, 0) of 374 | {error, {timeout, _}} -> 375 | % typical 376 | ok; 377 | ok -> 378 | % very infrequent 379 | ok 380 | end, 381 | ?PRINT_END. 382 | 383 | shutdown_blocking_unblocking_test() -> 384 | ?PRINT_START, 385 | {ok, C} = erlzmq:context(), 386 | {ok, _} = erlzmq:socket(C, [pub, {active, false}]), 387 | V = erlzmq:term(C, 0), 388 | ?assertMatch({error, {timeout, _}}, V), 389 | {error, {timeout, Ref}} = V, 390 | % all remaining sockets are automatically closed by term (i.e., zmq_term) 391 | receive 392 | {Ref, ok} -> 393 | ok 394 | end, 395 | ?PRINT_END. 396 | 397 | join_procs(0) -> 398 | ok; 399 | join_procs(N) -> 400 | receive 401 | proc_end -> 402 | join_procs(N-1) 403 | after 404 | 2000 -> 405 | throw(stuck) 406 | end. 407 | 408 | shutdown_stress_worker_loop(0, _) -> 409 | ok; 410 | shutdown_stress_worker_loop(N, C) -> 411 | {ok, S2} = erlzmq:socket(C, [sub, {active, false}]), 412 | spawn(?MODULE, worker, [self(), S2]), 413 | shutdown_stress_worker_loop(N-1, C). 414 | 415 | worker(Pid, S) -> 416 | ?assertMatch(ok, erlzmq:connect(S, "tcp://127.0.0.1:5558")), 417 | ?assertMatch(ok, erlzmq:close(S)), 418 | Pid ! proc_end. 419 | 420 | create_bound_pair(Ctx, Type1, Type2, Mode, Transport) -> 421 | Active = if 422 | Mode =:= active -> 423 | true; 424 | Mode =:= passive -> 425 | false 426 | end, 427 | {ok, S1} = erlzmq:socket(Ctx, [Type1, {active, Active}]), 428 | {ok, S2} = erlzmq:socket(Ctx, [Type2, {active, Active}]), 429 | ok = erlzmq:bind(S1, Transport), 430 | ok = erlzmq:connect(S2, Transport), 431 | {S1, S2}. 432 | 433 | ping_pong({S1, S2}, Msg, active) -> 434 | ok = erlzmq:send(S1, Msg, [sndmore]), 435 | ok = erlzmq:send(S1, Msg), 436 | receive 437 | {zmq, S2, Msg, [rcvmore]} -> 438 | ok 439 | after 440 | 1000 -> 441 | ?assertMatch({ok, Msg}, timeout) 442 | end, 443 | receive 444 | {zmq, S2, Msg, []} -> 445 | ok 446 | after 447 | 1000 -> 448 | ?assertMatch({ok, Msg}, timeout) 449 | end, 450 | ok = erlzmq:send(S2, Msg), 451 | receive 452 | {zmq, S1, Msg, []} -> 453 | ok 454 | after 455 | 1000 -> 456 | ?assertMatch({ok, Msg}, timeout) 457 | end, 458 | ok = erlzmq:send(S1, Msg), 459 | receive 460 | {zmq, S2, Msg, []} -> 461 | ok 462 | after 463 | 1000 -> 464 | ?assertMatch({ok, Msg}, timeout) 465 | end, 466 | ok = erlzmq:send(S2, Msg), 467 | receive 468 | {zmq, S1, Msg, []} -> 469 | ok 470 | after 471 | 1000 -> 472 | ?assertMatch({ok, Msg}, timeout) 473 | end, 474 | ok; 475 | 476 | ping_pong({S1, S2}, Msg, passive) -> 477 | ok = erlzmq:send(S1, Msg), 478 | ?assertMatch({ok, Msg}, erlzmq:recv(S2)), 479 | ok = erlzmq:send(S2, Msg), 480 | ?assertMatch({ok, Msg}, erlzmq:recv(S1)), 481 | ok = erlzmq:send(S1, Msg, [sndmore]), 482 | ok = erlzmq:send(S1, Msg), 483 | ?assertMatch({ok, Msg}, erlzmq:recv(S2)), 484 | ?assertMatch({ok, Msg}, erlzmq:recv(S2)), 485 | ok. 486 | 487 | basic_tests(Transport, Type1, Type2, Mode) -> 488 | {ok, C} = erlzmq:context(1), 489 | {S1, S2} = create_bound_pair(C, Type1, Type2, Mode, Transport), 490 | ping_pong({S1, S2}, <<"XXX">>, Mode), 491 | ok = erlzmq:close(S1), 492 | ok = erlzmq:close(S2), 493 | ok = erlzmq:term(C). 494 | 495 | --------------------------------------------------------------------------------