static void zmqdrv_send(zmq_drv_t *drv, ErlIOVec *ev) { ErlDrvBinary* bin = ev->binv[1]; char* bytes = bin->orig_bytes; uint32_t idx = ntohl(*(uint32_t*)(bytes+1)); zmq_sock_info* si = drv->get_socket_info(idx); uint32_t flags = ntohl(*(uint32_t*)(bytes+5)); void* data = (void *)(bytes + 9); size_t size = bin->orig_size - 9; if (idx > drv->zmq_socket_count || !si) { zmqdrv_error_code(drv, ENODEV); return; } #ifdef ZMQDRV_DEBUG uint32_t events; size_t events_size = sizeof(events); zmq_getsockopt(si->socket, ZMQ_EVENTS, &events, &events_size); zmqdrv_fprintf("sending %p [idx=%d] %lu bytes (events=%d)\r\n", si->socket, idx, size, events); #endif if (si->out_caller != 0) { // There's still an unwritten message pending zmqdrv_error_code(drv, EBUSY); return; } // Increment the reference count on binary so that zmq can // take ownership of it. driver_binary_inc_refc(bin); if (zmq_msg_init_data(&si->out_msg, data, size, &zmq_free_binary, bin)) { zmqdrv_error_code(drv, zmq_errno()); driver_binary_dec_refc(bin); return; } if (zmq_send(si->socket, &si->out_msg, flags | ZMQ_NOBLOCK) == 0) { zmqdrv_ok(drv); zmqdrv_ready_input((ErlDrvData)drv, (ErlDrvEvent)si->fd); } else { int e = zmq_errno(); if (e == EAGAIN) { // No msg returned to caller - make him wait until async // send succeeds si->out_caller = driver_caller(drv->port); return; } zmqdrv_error_code(drv, e); } zmq_msg_close(&si->out_msg); }
static void zmqdrv_setsockopt(zmq_drv_t *drv, ErlIOVec *ev) { ErlDrvBinary* bin = ev->binv[1]; char* bytes = bin->orig_bytes; uint32_t idx = ntohl(*(uint32_t*)(bytes+1)); zmq_sock_info* si = drv->get_socket_info(idx); uint8_t n = *(uint8_t*)(bytes+sizeof(idx)+1); char* p = bytes + 1 + sizeof(idx) + 1; if (idx > drv->zmq_socket_count || !si) { zmqdrv_error_code(drv, ENODEV); return; } zmqdrv_fprintf("setsockopt %p (setting %d options)\r\n", si->socket, (int)n); for (uint8_t j=0; j < n; ++j) { unsigned char option = *p++; uint64_t optvallen = *p++; void* optval = p; switch (option) { case ZMQ_HWM: assert(optvallen == 8); break; case ZMQ_SWAP: assert(optvallen == 8); break; case ZMQ_AFFINITY: assert(optvallen == 8); break; case ZMQ_IDENTITY: assert(optvallen < 256); break; case ZMQ_SUBSCRIBE: assert(optvallen < 256); break; case ZMQ_UNSUBSCRIBE: assert(optvallen < 256); break; case ZMQ_RATE: assert(optvallen == 8); break; case ZMQ_RECOVERY_IVL: assert(optvallen == 8); break; case ZMQ_MCAST_LOOP: assert(optvallen == 8); break; case ZMQ_SNDBUF: assert(optvallen == 8); break; case ZMQ_RCVBUF: assert(optvallen == 8); break; case ZMQ_ACTIVE: assert(optvallen == 1); break; } zmqdrv_fprintf("setsockopt %p (%d)\r\n", si->socket, option); if (option == ZMQ_ACTIVE) si->active_mode = *(char*)optval; else if (zmq_setsockopt(si->socket, option, optval, optvallen) < 0) { zmqdrv_error_code(drv, zmq_errno()); return; } p += optvallen; } zmqdrv_ok(drv); }
static void zmqdrv_term(zmq_drv_t *drv, ErlIOVec *ev) { if (!drv->zmq_context) { zmqdrv_error_code(drv, ENODEV); return; } zmqdrv_fprintf("calling zmq_term(context) ...\r\n"); int rc = zmq_term(drv->zmq_context); zmqdrv_fprintf("terminated zmq context\r\n"); if (rc < 0) { zmqdrv_error_code(drv, zmq_errno()); return; } zmqdrv_ok(drv); drv->zmq_context = NULL; }
static void zmqdrv_init(zmq_drv_t *drv, ErlIOVec *ev) { /* * FIXME * Use ei_decode_* to decode input from erlang VM. * This stuff is not documented anywhere, for now * binary ErlIOVec is decoded by poking in iov struct. * * Serge: Dhammika, ei_decode can only be used to decode * external binary format in the "output" callback function. * It's not suitable for using inside "outputv" body that * operates on I/O vectors unless you use term_to_binary/1 * call to explicitely convert a term to external binary format. */ uint32_t io_threads; ErlDrvBinary* input = ev->binv[1]; char* bytes = input->orig_bytes; io_threads = ntohl(*(uint32_t *)(bytes + 1)); zmqdrv_fprintf("iothreads = %u\r\n", io_threads); if (drv->zmq_context) { zmqdrv_error_code(drv, EBUSY); return; } drv->zmq_context = (void *)zmq_init(io_threads); if (!drv->zmq_context) { zmqdrv_error_code(drv, zmq_errno()); return; } zmqdrv_ok(drv); }
static void zmqdrv_close(zmq_drv_t *drv, ErlIOVec *ev) { ErlDrvBinary* bin = ev->binv[1]; char* bytes = bin->orig_bytes; uint32_t idx = ntohl(*(uint32_t*)(bytes+1)); if (idx > drv->zmq_socket_count) { zmqdrv_error_code(drv, ENODEV); return; } int ret = drv->del_socket(idx); zmqdrv_fprintf("close [idx=%d] -> %d\r\n", idx, ret); if (ret < 0) { zmqdrv_error_code(drv, zmq_errno()); return; } zmqdrv_ok(drv); }
static void zmqdrv_connect(zmq_drv_t *drv, ErlIOVec *ev) { ErlDrvBinary* bin = ev->binv[1]; char* bytes = bin->orig_bytes; uint32_t idx = ntohl(*(uint32_t*)(bytes+1)); void* s = drv->get_zmq_socket(idx); uint16_t size = bin->orig_size - 5; char addr[512]; if (idx > drv->zmq_socket_count || !s) { zmqdrv_error_code(drv, ENODEV); return; } if (size > sizeof(addr) - 1) { zmqdrv_error_code(drv, E2BIG); return; } memcpy(addr, bytes + 5, size); addr[size] = '\0'; zmqdrv_fprintf("connect %s\r\n", addr); if (!addr[0]) { zmqdrv_error_code(drv, EINVAL); return; } if (zmq_connect(s, addr) < 0) { zmqdrv_error_code(drv, zmq_errno()); return; } zmqdrv_ok(drv); }
static void zmqdrv_ready_input(ErlDrvData handle, ErlDrvEvent event) { zmq_drv_t *drv = (zmq_drv_t *)handle; // Get 0MQ sockets managed by application thread's signaler // identified by "event" fd. zmq_fd_sockets_map_t::iterator it = drv->zmq_fd_sockets.find((long)event); zmqdrv_fprintf("input ready on [idx=%ld]\r\n", (long)event); assert(it != drv->zmq_fd_sockets.end()); zmq_sock_set_t::iterator si = it->second.begin(); assert(si != it->second.end()); for (; si != it->second.end(); ++si) { zmq_socket_t s = (*si)->socket; uint32_t idx = (*si)->idx; ErlDrvTermData owner = (*si)->owner; int rc = 0; uint32_t events; size_t events_size = sizeof(events); zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); while (((*si)->active_mode || (*si)->in_caller) && (events & ZMQ_POLLIN)) { msg_t msg; rc = zmq_recv(s, &msg, ZMQ_NOBLOCK); ErlDrvTermData pid = (*si)->active_mode ? owner : (*si)->in_caller; if (rc == -1) { if (zmq_errno() != EAGAIN) { ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zmq, ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_UINT, idx, ERL_DRV_TUPLE, 2, ERL_DRV_ATOM, am_error, ERL_DRV_ATOM, error_atom(zmq_errno()), ERL_DRV_TUPLE, 2, ERL_DRV_TUPLE, 3}; driver_send_term(drv->port, owner, spec, sizeof(spec)/sizeof(spec[0])); (*si)->in_caller = 0; } break; } if ((*si)->active_mode == 1) { // Send message {zmq, Socket, binary()} to the owner pid ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zmq, ERL_DRV_PORT, driver_mk_port(drv->port), ERL_DRV_UINT, idx, ERL_DRV_TUPLE, 2, ERL_DRV_BUF2BINARY, (ErlDrvTermData)zmq_msg_data(&msg), zmq_msg_size(&msg), ERL_DRV_TUPLE, 3}; driver_send_term(drv->port, owner, spec, sizeof(spec)/sizeof(spec[0])); } else if ((*si)->active_mode == 2) { // Send message {zmq, Socket, [binary()]} to the owner pid // where binary() is each message part std::vector<ErlDrvTermData> specv; specv.reserve(100); std::vector<msg_t*> parts; specv.push_back(ERL_DRV_ATOM); specv.push_back(am_zmq); specv.push_back(ERL_DRV_PORT); specv.push_back(driver_mk_port(drv->port)); specv.push_back(ERL_DRV_UINT); specv.push_back(idx); specv.push_back(ERL_DRV_TUPLE); specv.push_back(2); specv.push_back(ERL_DRV_BUF2BINARY); specv.push_back((ErlDrvTermData)zmq_msg_data(&msg)); specv.push_back(zmq_msg_size(&msg)); int64_t more; size_t more_size = sizeof(more); int next_count = 0; while (true) { zmq_getsockopt(s, ZMQ_RCVMORE, &more, &more_size); if (!more) break; next_count += 1; msg_t *next_part = new msg_t; parts.push_back(next_part); rc = zmq_recv(s, next_part, ZMQ_NOBLOCK); assert (!rc); // FIXME specv.push_back(ERL_DRV_BUF2BINARY); specv.push_back((ErlDrvTermData)zmq_msg_data(next_part)); specv.push_back(zmq_msg_size(next_part)); } specv.push_back(ERL_DRV_NIL); specv.push_back(ERL_DRV_LIST); specv.push_back(next_count + 2); specv.push_back(ERL_DRV_TUPLE); specv.push_back(3); driver_send_term(drv->port, owner, specv.data(), specv.size()); for (uint i = 0; i < parts.size(); i++) { delete parts[i]; } } else { // Return result {ok, binary()} to the waiting caller's pid ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zok, ERL_DRV_BUF2BINARY, (ErlDrvTermData)zmq_msg_data(&msg), zmq_msg_size(&msg), ERL_DRV_TUPLE, 2}; driver_send_term(drv->port, pid, spec, sizeof(spec)/sizeof(spec[0])); (*si)->in_caller = 0; } // FIXME: add error handling zmqdrv_fprintf("received %ld byte message relayed to pid %ld\r\n", zmq_msg_size(&msg), pid); zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); } zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); if ((*si)->out_caller != 0 && (events & ZMQ_POLLOUT)) { // There was a pending unwritten message on this socket. // Try to write it. If the write succeeds/fails clear the ZMQ_POLLOUT // flag and notify the waiting caller of completion of operation. rc = zmq_send(s, &(*si)->out_msg, (*si)->out_flags | ZMQ_NOBLOCK); zmqdrv_fprintf("resending message %p (size=%ld) on socket %p (ret=%d)\r\n", zmq_msg_data(&(*si)->out_msg), zmq_msg_size(&(*si)->out_msg), s, rc); if (rc == 0) { zmq_msg_close(&(*si)->out_msg); // Unblock the waiting caller's pid by returning result zmqdrv_ok(drv, (*si)->out_caller); (*si)->out_caller = 0; } else if (zmq_errno() != EAGAIN) { // Unblock the waiting caller's pid by returning result zmq_msg_close(&(*si)->out_msg); zmqdrv_socket_error(drv, (*si)->out_caller, idx, zmq_errno()); (*si)->out_caller = 0; } } zmqdrv_fprintf("--> socket %p events=%d\r\n", s, events); } }
static void zmqdrv_ok(zmq_drv_t *drv) { zmqdrv_ok(drv, driver_caller(drv->port)); }
static void zmqdrv_ready_input(ErlDrvData handle, ErlDrvEvent event) { zmq_drv_t *drv = (zmq_drv_t *)handle; // Get 0MQ sockets managed by application thread's signaler // identified by "event" fd. zmq_fd_sockets_map_t::iterator it = drv->zmq_fd_sockets.find((long)event); zmqdrv_fprintf("input ready on [idx=%ld]\r\n", (long)event); assert(it != drv->zmq_fd_sockets.end()); zmq_sock_set_t::iterator si = it->second.begin(); assert(si != it->second.end()); for (; si != it->second.end(); ++si) { zmq_socket_t s = (*si)->socket; uint32_t idx = (*si)->idx; ErlDrvTermData owner = (*si)->owner; int rc = 0; uint32_t events; size_t events_size = sizeof(events); zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); while (((*si)->active_mode || (*si)->in_caller) && (events & ZMQ_POLLIN)) { msg_t msg; rc = zmq_recv(s, &msg, ZMQ_NOBLOCK); ErlDrvTermData pid = (*si)->active_mode ? owner : (*si)->in_caller; if (rc == -1) { if (zmq_errno() != EAGAIN) { ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zmq, ERL_DRV_UINT, idx, ERL_DRV_ATOM, error_atom(zmq_errno()), ERL_DRV_TUPLE, 2, ERL_DRV_TUPLE, 3}; driver_send_term(drv->port, owner, spec, sizeof(spec)/sizeof(spec[0])); (*si)->in_caller = 0; } break; } if ((*si)->active_mode) { // Send message {zmq, Socket, binary()} to the owner pid ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zmq, ERL_DRV_UINT, idx, ERL_DRV_BUF2BINARY, (ErlDrvTermData)zmq_msg_data(&msg), zmq_msg_size(&msg), ERL_DRV_TUPLE, 3}; driver_send_term(drv->port, owner, spec, sizeof(spec)/sizeof(spec[0])); } else { // Return result {ok, binary()} to the waiting caller's pid ErlDrvTermData spec[] = {ERL_DRV_ATOM, am_zok, ERL_DRV_BUF2BINARY, (ErlDrvTermData)zmq_msg_data(&msg), zmq_msg_size(&msg), ERL_DRV_TUPLE, 2}; driver_send_term(drv->port, pid, spec, sizeof(spec)/sizeof(spec[0])); (*si)->in_caller = 0; } // FIXME: add error handling zmqdrv_fprintf("received %ld byte message relayed to pid %ld\r\n", zmq_msg_size(&msg), pid); zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); } zmq_getsockopt(s, ZMQ_EVENTS, &events, &events_size); if ((*si)->out_caller != 0 && (events & ZMQ_POLLOUT)) { // There was a pending unwritten message on this socket. // Try to write it. If the write succeeds/fails clear the ZMQ_POLLOUT // flag and notify the waiting caller of completion of operation. rc = zmq_send(s, &(*si)->out_msg, (*si)->out_flags | ZMQ_NOBLOCK); zmqdrv_fprintf("resending message %p (size=%ld) on socket %p (ret=%d)\r\n", zmq_msg_data(&(*si)->out_msg), zmq_msg_size(&(*si)->out_msg), s, rc); if (rc == 0) { zmq_msg_close(&(*si)->out_msg); // Unblock the waiting caller's pid by returning result zmqdrv_ok(drv, (*si)->out_caller); (*si)->out_caller = 0; } else if (zmq_errno() != EAGAIN) { // Unblock the waiting caller's pid by returning result zmq_msg_close(&(*si)->out_msg); zmqdrv_socket_error(drv, (*si)->out_caller, idx, zmq_errno()); (*si)->out_caller = 0; } } zmqdrv_fprintf("--> socket %p events=%d\r\n", s, events); } }