void
dispatch_t::on_message(const io::message_t& message) {
    COCAINE_LOG_DEBUG(service_manager()->get_system_logger(),
                      "worker %s received type %d message",
                      m_id, message.id());

    switch(message.id()) {
        case io::event_traits<io::rpc::heartbeat>::id: {
            m_disown_timer.stop();
            break;
        }
        case io::event_traits<io::rpc::invoke>::id: {
            std::string event;
            message.as<io::rpc::invoke>(event);

            COCAINE_LOG_DEBUG(service_manager()->get_system_logger(),
                              "worker %s invoking session %d with event '%s'",
                              m_id,
                              message.band(),
                              event);

            std::shared_ptr<detail::dispatch_upstream_t> upstream(
                std::make_shared<detail::dispatch_upstream_t>(message.band(), this)
            );

            try {
                io_pair_t session = {upstream, invoke(event, upstream)};
                m_sessions.insert(std::make_pair(message.band(), session));
            } catch(const std::exception& e) {
                upstream->error(invocation_error, e.what());
            } catch(...) {
                upstream->error(invocation_error, "unexpected exception");
            }

            break;
        }
        case io::event_traits<io::rpc::chunk>::id: {
            std::string chunk;
            message.as<io::rpc::chunk>(chunk);

            stream_map_t::iterator it = m_sessions.find(message.band());

            // NOTE: This may be a chunk for a failed invocation, in which case there
            // will be no active stream, so drop the message.
            if(it != m_sessions.end()) {
                try {
                    it->second.handler->on_chunk(chunk.data(), chunk.size());
                } catch(const std::exception& e) {
                    it->second.upstream->error(invocation_error, e.what());
                    m_sessions.erase(it);
                } catch(...) {
                    it->second.upstream->error(invocation_error, "unexpected exception");
                    m_sessions.erase(it);
                }
            }

            break;
        }
        case io::event_traits<io::rpc::choke>::id: {
            stream_map_t::iterator it = m_sessions.find(message.band());

            // NOTE: This may be a choke for a failed invocation, in which case there
            // will be no active stream, so drop the message.
            if(it != m_sessions.end()) {
                try {
                    it->second.handler->on_close();
                } catch(const std::exception& e) {
                    it->second.upstream->error(invocation_error, e.what());
                } catch(...) {
                    it->second.upstream->error(invocation_error, "unexpected exception");
                }

                m_sessions.erase(it);
            }

            break;
        }
        case io::event_traits<io::rpc::error>::id: {
            int ec;
            std::string error_message;
            message.as<io::rpc::error>(ec, error_message);

            stream_map_t::iterator it = m_sessions.find(message.band());

            // NOTE: This may be a chunk for a failed invocation, in which case there
            // will be no active stream, so drop the message.
            if(it != m_sessions.end()) {
                try {
                    it->second.handler->on_error(ec, error_message);
                } catch(const std::exception& e) {
                    it->second.upstream->error(invocation_error, e.what());
                    m_sessions.erase(it);
                } catch(...) {
                    it->second.upstream->error(invocation_error, "unexpected exception");
                    m_sessions.erase(it);
                }
            }

            break;
        }
        case io::event_traits<io::rpc::terminate>::id: {
            terminate(io::rpc::terminate::normal, "per request");
            break;
        }
        default: {
            COCAINE_LOG_WARNING(service_manager()->get_system_logger(),
                                "worker %s dropping unknown type %d message",
                                m_id,
                                message.id());
        }
    }
}
void
worker_t::on_message(const io::message_t& message) {
    COCAINE_LOG_DEBUG(
        m_log,
        "worker %s received type %d message",
        m_id,
        message.id()
    );

    switch(message.id()) {
        case event_traits<rpc::heartbeat>::id:
            m_disown_timer.stop();
            m_disown_timer.start(m_profile->heartbeat_timeout);

            break;

        case event_traits<rpc::invoke>::id: {
            uint64_t session_id;
            std::string event;

            message.as<rpc::invoke>(session_id, event);

            COCAINE_LOG_DEBUG(m_log, "worker %s invoking session %s with event '%s'", m_id, session_id, event);

            std::shared_ptr<api::stream_t> upstream(
                std::make_shared<upstream_t>(session_id, this)
            );

            try {
                io_pair_t io = {
                    upstream,
                    m_sandbox->invoke(event, upstream)
                };

                m_streams.insert(std::make_pair(session_id, io));
            } catch(const std::exception& e) {
                upstream->error(invocation_error, e.what());
            } catch(...) {
                upstream->error(invocation_error, "unexpected exception");
            }

            break;
        }

        case event_traits<rpc::chunk>::id: {
            uint64_t session_id;
            std::string chunk;

            message.as<rpc::chunk>(session_id, chunk);

            stream_map_t::iterator it(m_streams.find(session_id));

            // NOTE: This may be a chunk for a failed invocation, in which case there
            // will be no active stream, so drop the message.
            if(it != m_streams.end()) {
                try {
                    it->second.downstream->push(chunk.data(), chunk.size());
                } catch(const std::exception& e) {
                    it->second.upstream->error(invocation_error, e.what());
                    m_streams.erase(it);
                } catch(...) {
                    it->second.upstream->error(invocation_error, "unexpected exception");
                    m_streams.erase(it);
                }
            }

            break;
        }

        case event_traits<rpc::choke>::id: {
            uint64_t session_id;

            message.as<rpc::choke>(session_id);

            stream_map_t::iterator it = m_streams.find(session_id);

            // NOTE: This may be a choke for a failed invocation, in which case there
            // will be no active stream, so drop the message.
            if(it != m_streams.end()) {
                try {
                    it->second.downstream->close();
                } catch(const std::exception& e) {
                    it->second.upstream->error(invocation_error, e.what());
                } catch(...) {
                    it->second.upstream->error(invocation_error, "unexpected exception");
                }

                m_streams.erase(it);
            }

            break;
        }

        case event_traits<rpc::terminate>::id:
            terminate(rpc::terminate::normal, "per request");
            break;

        default:
            COCAINE_LOG_WARNING(
                m_log,
                "worker %s dropping unknown type %d message",
                m_id,
                message.id()
            );
    }
}