Update levin async callbacks for C++14

This commit is contained in:
Lee *!* Clagett 2022-08-29 15:03:42 -04:00
parent c9cfa25183
commit a263cae14a
6 changed files with 93 additions and 117 deletions

View File

@ -379,7 +379,7 @@ namespace net_utils
try_connect_result_t try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_support_t ssl_support); try_connect_result_t try_connect(connection_ptr new_connection_l, const std::string& adr, const std::string& port, boost::asio::ip::tcp::socket &sock_, const boost::asio::ip::tcp::endpoint &remote_endpoint, const std::string &bind_ip, uint32_t conn_timeout, epee::net_utils::ssl_support_t ssl_support);
bool connect(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_connection_context& cn, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); bool connect(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_connection_context& cn, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
template<class t_callback> template<class t_callback>
bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, const t_callback &cb, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_callback &&cb, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect);
boost::asio::ssl::context& get_ssl_context() noexcept boost::asio::ssl::context& get_ssl_context() noexcept
{ {

View File

@ -1786,7 +1786,7 @@ namespace net_utils
} }
//--------------------------------------------------------------------------------- //---------------------------------------------------------------------------------
template<class t_protocol_handler> template<class t_callback> template<class t_protocol_handler> template<class t_callback>
bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support) bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, t_callback &&cb, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support)
{ {
TRY_ENTRY(); TRY_ENTRY();
connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) ); connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) );
@ -1879,7 +1879,7 @@ namespace net_utils
} }
}); });
//start async connect //start async connect
sock_.async_connect(remote_endpoint, [=](const boost::system::error_code& ec_) sock_.async_connect(remote_endpoint, [=, cb = std::forward<t_callback>(cb)](const boost::system::error_code& ec_) mutable
{ {
t_connection_context conn_context = AUTO_VAL_INIT(conn_context); t_connection_context conn_context = AUTO_VAL_INIT(conn_context);
boost::system::error_code ignored_ec; boost::system::error_code ignored_ec;

View File

@ -104,7 +104,7 @@ public:
int invoke(int command, message_writer in_msg, std::string& buff_out, boost::uuids::uuid connection_id); int invoke(int command, message_writer in_msg, std::string& buff_out, boost::uuids::uuid connection_id);
template<class callback_t> template<class callback_t>
int invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); int invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, callback_t &&cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED);
int send(epee::byte_slice message, const boost::uuids::uuid& connection_id); int send(epee::byte_slice message, const boost::uuids::uuid& connection_id);
bool close(boost::uuids::uuid connection_id); bool close(boost::uuids::uuid connection_id);
@ -190,105 +190,90 @@ public:
struct invoke_response_handler_base struct invoke_response_handler_base
{ {
virtual bool handle(int res, const epee::span<const uint8_t> buff, connection_context& context)=0; protected:
virtual bool is_timer_started() const=0;
virtual void cancel()=0;
virtual bool cancel_timer()=0;
virtual void reset_timer()=0;
};
template <class callback_t>
struct anvoke_handler: invoke_response_handler_base
{
anvoke_handler(const callback_t& cb, uint64_t timeout, async_protocol_handler& con, int command)
:m_cb(cb), m_timeout(timeout), m_con(con), m_timer(con.m_pservice_endpoint->get_io_service()), m_timer_started(false),
m_cancel_timer_called(false), m_timer_cancelled(false), m_command(command)
{
if(m_con.start_outer_call())
{
MDEBUG(con.get_context_ref() << "anvoke_handler, timeout: " << timeout);
m_timer.expires_from_now(boost::posix_time::milliseconds(timeout));
m_timer.async_wait([&con, command, cb, timeout](const boost::system::error_code& ec)
{
if(ec == boost::asio::error::operation_aborted)
return;
MINFO(con.get_context_ref() << "Timeout on invoke operation happened, command: " << command << " timeout: " << timeout);
epee::span<const uint8_t> fake;
cb(LEVIN_ERROR_CONNECTION_TIMEDOUT, fake, con.get_context_ref());
con.close();
con.finish_outer_call();
});
m_timer_started = true;
}
}
virtual ~anvoke_handler()
{}
callback_t m_cb;
async_protocol_handler& m_con;
boost::asio::deadline_timer m_timer; boost::asio::deadline_timer m_timer;
bool m_timer_started; async_protocol_handler& m_con;
bool m_cancel_timer_called; const uint64_t m_timeout;
bool m_timer_cancelled; const int m_command;
uint64_t m_timeout;
int m_command; virtual void do_handle(int res, const epee::span<const uint8_t> buff)=0;
virtual bool handle(int res, const epee::span<const uint8_t> buff, typename async_protocol_handler::connection_context& context)
public:
invoke_response_handler_base(uint64_t timeout, async_protocol_handler& con, int command)
: m_timer(con.m_pservice_endpoint->get_io_service()),
m_con(con),
m_timeout(timeout),
m_command(command)
{}
virtual ~invoke_response_handler_base() {}
//! Run user handler. Only call if `cancel_timer()` returns true.
void handle(int res, const epee::span<const uint8_t> buff)
{ {
if(!cancel_timer()) do_handle(res, buff);
return false;
m_cb(res, buff, context);
m_con.finish_outer_call(); m_con.finish_outer_call();
}
//! Cancel timer and run user handler if not previously invoked \return True if user handler invoked
bool cancel()
{
if (!cancel_timer())
return false;
handle(LEVIN_ERROR_CONNECTION_DESTROYED, nullptr);
return true; return true;
} }
virtual bool is_timer_started() const
//! Cancel timer but do not run user handler. \return True if user handler has not been invoked
bool cancel_timer()
{ {
return m_timer_started; boost::system::error_code ignored{};
return m_timer.cancel(ignored);
} }
virtual void cancel()
// Attempt to adjust timeout for user handler execution. \return True if timer adjusted.
static bool reset_timer(const std::shared_ptr<invoke_response_handler_base>& self, const bool initial = false)
{ {
if(cancel_timer()) if (!self || (!initial && !self->cancel_timer()))
return false;
if (initial && !self->m_con.start_outer_call())
return false;
self->m_timer.expires_from_now(boost::posix_time::milliseconds(self->m_timeout));
self->m_timer.async_wait([self](const boost::system::error_code& ec)
{ {
epee::span<const uint8_t> fake; if(ec == boost::asio::error::operation_aborted)
m_cb(LEVIN_ERROR_CONNECTION_DESTROYED, fake, m_con.get_context_ref()); return;
m_con.finish_outer_call(); MINFO(self->m_con.get_context_ref() << "Timeout on invoke operation happened, command: " << self->m_command << " timeout: " << self->m_timeout);
} self->do_handle(LEVIN_ERROR_CONNECTION_TIMEDOUT, nullptr); // delay finish_outer_call
self->m_con.close();
self->m_con.finish_outer_call();
});
return true;
} }
virtual bool cancel_timer() };
template <class callback_t>
struct anvoke_handler final : invoke_response_handler_base
{
template<typename F>
anvoke_handler(F&& cb, uint64_t timeout, async_protocol_handler& con, int command)
: invoke_response_handler_base(timeout, con, command), m_cb(std::forward<F>(cb))
{}
virtual ~anvoke_handler() override final
{}
private:
callback_t m_cb;
virtual void do_handle(int res, const epee::span<const uint8_t> buff) override final
{ {
if(!m_cancel_timer_called) m_cb(res, buff, this->m_con.get_context_ref());
{
m_cancel_timer_called = true;
boost::system::error_code ignored_ec;
m_timer_cancelled = 1 == m_timer.cancel(ignored_ec);
}
return m_timer_cancelled;
}
virtual void reset_timer()
{
boost::system::error_code ignored_ec;
if (!m_cancel_timer_called && m_timer.cancel(ignored_ec) > 0)
{
callback_t& cb = m_cb;
uint64_t timeout = m_timeout;
async_protocol_handler& con = m_con;
int command = m_command;
m_timer.expires_from_now(boost::posix_time::milliseconds(m_timeout));
m_timer.async_wait([&con, cb, command, timeout](const boost::system::error_code& ec)
{
if(ec == boost::asio::error::operation_aborted)
return;
MINFO(con.get_context_ref() << "Timeout on invoke operation happened, command: " << command << " timeout: " << timeout);
epee::span<const uint8_t> fake;
cb(LEVIN_ERROR_CONNECTION_TIMEDOUT, fake, con.get_context_ref());
con.close();
con.finish_outer_call();
});
}
} }
}; };
critical_section m_invoke_response_handlers_lock; critical_section m_invoke_response_handlers_lock;
std::list<boost::shared_ptr<invoke_response_handler_base> > m_invoke_response_handlers; std::deque<std::shared_ptr<invoke_response_handler_base>> m_invoke_response_handlers;
template<class callback_t> template<class callback_t>
bool add_invoke_response_handler(const callback_t &cb, uint64_t timeout, async_protocol_handler& con, int command) bool add_invoke_response_handler(callback_t &&cb, uint64_t timeout, async_protocol_handler& con, int command)
{ {
CRITICAL_REGION_LOCAL(m_invoke_response_handlers_lock); CRITICAL_REGION_LOCAL(m_invoke_response_handlers_lock);
if (m_protocol_released) if (m_protocol_released)
@ -296,9 +281,10 @@ public:
MERROR("Adding response handler to a released object"); MERROR("Adding response handler to a released object");
return false; return false;
} }
boost::shared_ptr<invoke_response_handler_base> handler(boost::make_shared<anvoke_handler<callback_t>>(cb, timeout, con, command)); m_invoke_response_handlers.push_back(
m_invoke_response_handlers.push_back(handler); std::make_shared<anvoke_handler<std::decay_t<callback_t>>>(std::forward<callback_t>(cb), timeout, con, command)
return handler->is_timer_started(); );
return invoke_response_handler_base::reset_timer(m_invoke_response_handlers.back(), true);
} }
template<class callback_t> friend struct anvoke_handler; template<class callback_t> friend struct anvoke_handler;
public: public:
@ -372,9 +358,8 @@ public:
// Never call callback inside critical section, that can cause deadlock. Callback can be called when // Never call callback inside critical section, that can cause deadlock. Callback can be called when
// invoke_response_handler_base is cancelled // invoke_response_handler_base is cancelled
std::for_each(local_invoke_response_handlers.begin(), local_invoke_response_handlers.end(), [](const boost::shared_ptr<invoke_response_handler_base>& pinv_resp_hndlr) { for (const std::shared_ptr<invoke_response_handler_base>& pinv_resp_hndlr : local_invoke_response_handlers)
pinv_resp_hndlr->cancel(); pinv_resp_hndlr->cancel();
});
return true; return true;
} }
@ -447,8 +432,7 @@ public:
if (!m_invoke_response_handlers.empty()) if (!m_invoke_response_handlers.empty())
{ {
//async call scenario //async call scenario
boost::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front(); invoke_response_handler_base::reset_timer(m_invoke_response_handlers.front());
response_handler->reset_timer();
MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb << ", current total " << m_cache_in_buffer.size() << "/" << m_current_head.m_cb << " (" << (100.0f * m_cache_in_buffer.size() / (m_current_head.m_cb ? m_current_head.m_cb : 1)) << "%)"); MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb << ", current total " << m_cache_in_buffer.size() << "/" << m_current_head.m_cb << " (" << (100.0f * m_cache_in_buffer.size() / (m_current_head.m_cb ? m_current_head.m_cb : 1)) << "%)");
} }
} }
@ -509,7 +493,7 @@ public:
epee::critical_region_t<decltype(m_invoke_response_handlers_lock)> invoke_response_handlers_guard(m_invoke_response_handlers_lock); epee::critical_region_t<decltype(m_invoke_response_handlers_lock)> invoke_response_handlers_guard(m_invoke_response_handlers_lock);
if(!m_invoke_response_handlers.empty()) if(!m_invoke_response_handlers.empty())
{//async call scenario {//async call scenario
boost::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front(); const std::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front();
bool timer_cancelled = response_handler->cancel_timer(); bool timer_cancelled = response_handler->cancel_timer();
// Don't pop handler, to avoid destroying it // Don't pop handler, to avoid destroying it
if(timer_cancelled) if(timer_cancelled)
@ -517,7 +501,7 @@ public:
invoke_response_handlers_guard.unlock(); invoke_response_handlers_guard.unlock();
if(timer_cancelled) if(timer_cancelled)
response_handler->handle(m_current_head.m_return_code, buff_to_invoke, m_connection_context); response_handler->handle(m_current_head.m_return_code, buff_to_invoke);
} }
else else
{ {
@ -628,7 +612,7 @@ public:
} }
template<class callback_t> template<class callback_t>
bool async_invoke(int command, message_writer in_msg, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) bool async_invoke(int command, message_writer in_msg, callback_t &&cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED)
{ {
misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler( misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler(
boost::bind(&async_protocol_handler::finish_outer_call, this)); boost::bind(&async_protocol_handler::finish_outer_call, this));
@ -654,7 +638,7 @@ public:
break; break;
} }
if(!add_invoke_response_handler(cb, timeout, *this, command)) if(!add_invoke_response_handler(std::forward<callback_t>(cb), timeout, *this, command))
{ {
err_code = LEVIN_ERROR_CONNECTION_DESTROYED; err_code = LEVIN_ERROR_CONNECTION_DESTROYED;
break; break;
@ -836,11 +820,11 @@ int async_protocol_handler_config<t_connection_context>::invoke(int command, mes
} }
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
template<class t_connection_context> template<class callback_t> template<class t_connection_context> template<class callback_t>
int async_protocol_handler_config<t_connection_context>::invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout) int async_protocol_handler_config<t_connection_context>::invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, callback_t &&cb, size_t timeout)
{ {
async_protocol_handler<t_connection_context>* aph; async_protocol_handler<t_connection_context>* aph;
int r = find_and_lock_connection(connection_id, aph); int r = find_and_lock_connection(connection_id, aph);
return LEVIN_OK == r ? aph->async_invoke(command, std::move(in_msg), cb, timeout) : r; return LEVIN_OK == r ? aph->async_invoke(command, std::move(in_msg), std::forward<callback_t>(cb), timeout) : r;
} }
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
template<class t_connection_context> template<class callback_t> template<class t_connection_context> template<class callback_t>

View File

@ -84,14 +84,14 @@ namespace epee
} }
template<class t_result, class t_arg, class callback_t, class t_transport> template<class t_result, class t_arg, class callback_t, class t_transport>
bool async_invoke_remote_command2(const epee::net_utils::connection_context_base &context, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) bool async_invoke_remote_command2(const epee::net_utils::connection_context_base &context, int command, const t_arg& out_struct, t_transport& transport, callback_t &&cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED)
{ {
const boost::uuids::uuid &conn_id = context.m_connection_id; const boost::uuids::uuid &conn_id = context.m_connection_id;
typename serialization::portable_storage stg; typename serialization::portable_storage stg;
const_cast<t_arg&>(out_struct).store(stg);//TODO: add true const support to searilzation const_cast<t_arg&>(out_struct).store(stg);//TODO: add true const support to searilzation
levin::message_writer to_send{16 * 1024}; levin::message_writer to_send{16 * 1024};
stg.store_to_binary(to_send.buffer); stg.store_to_binary(to_send.buffer);
int res = transport.invoke_async(command, std::move(to_send), conn_id, [cb, command](int code, const epee::span<const uint8_t> buff, typename t_transport::connection_context& context)->bool int res = transport.invoke_async(command, std::move(to_send), conn_id, [cb = std::forward<callback_t>(cb), command](int code, const epee::span<const uint8_t> buff, typename t_transport::connection_context& context)->bool
{ {
t_result result_struct = AUTO_VAL_INIT(result_struct); t_result result_struct = AUTO_VAL_INIT(result_struct);
if( code <=0 ) if( code <=0 )

View File

@ -385,7 +385,7 @@ namespace nodetool
void delete_upnp_port_mapping_v6(uint32_t port); void delete_upnp_port_mapping_v6(uint32_t port);
void delete_upnp_port_mapping(uint32_t port); void delete_upnp_port_mapping(uint32_t port);
template<class t_callback> template<class t_callback>
bool try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb); bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback &&cb);
bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f);
bool make_expected_connections_count(network_zone& zone, PeerType peer_type, size_t expected_connections); bool make_expected_connections_count(network_zone& zone, PeerType peer_type, size_t expected_connections);
void record_addr_failed(const epee::net_utils::network_address& addr); void record_addr_failed(const epee::net_utils::network_address& addr);

View File

@ -2321,7 +2321,7 @@ namespace nodetool
} }
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
template<class t_payload_net_handler> template<class t_callback> template<class t_payload_net_handler> template<class t_callback>
bool node_server<t_payload_net_handler>::try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb) bool node_server<t_payload_net_handler>::try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback &&cb)
{ {
if(!node_data.my_port) if(!node_data.my_port)
return false; return false;
@ -2364,9 +2364,9 @@ namespace nodetool
address = epee::net_utils::network_address{epee::net_utils::ipv6_network_address(ipv6_addr, node_data.my_port)}; address = epee::net_utils::network_address{epee::net_utils::ipv6_network_address(ipv6_addr, node_data.my_port)};
} }
peerid_type pr = node_data.peer_id; peerid_type pr = node_data.peer_id;
bool r = zone.m_net_server.connect_async(ip, port, zone.m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( bool r = zone.m_net_server.connect_async(ip, port, zone.m_config.m_net_config.ping_connection_timeout, [cb = std::forward<t_callback>(cb), /*context,*/ address, pr, this] (
const typename net_server::t_connection_context& ping_context, const typename net_server::t_connection_context& ping_context,
const boost::system::error_code& ec)->bool const boost::system::error_code& ec) mutable ->bool
{ {
if(ec) if(ec)
{ {
@ -2375,19 +2375,11 @@ namespace nodetool
} }
COMMAND_PING::request req; COMMAND_PING::request req;
COMMAND_PING::response rsp; COMMAND_PING::response rsp;
//vc2010 workaround
/*std::string ip_ = ip;
std::string port_=port;
peerid_type pr_ = pr;
auto cb_ = cb;*/
// GCC 5.1.0 gives error with second use of uint64_t (peerid_type) variable.
peerid_type pr_ = pr;
network_zone& zone = m_network_zones.at(address.get_zone()); network_zone& zone = m_network_zones.at(address.get_zone());
bool inv_call_res = epee::net_utils::async_invoke_remote_command2<COMMAND_PING::response>(ping_context, COMMAND_PING::ID, req, zone.m_net_server.get_config_object(), bool inv_call_res = epee::net_utils::async_invoke_remote_command2<COMMAND_PING::response>(ping_context, COMMAND_PING::ID, req, zone.m_net_server.get_config_object(),
[=](int code, const COMMAND_PING::response& rsp, p2p_connection_context& context) [this, pr, cb = std::move(cb), ping_context = std::move(ping_context), address = std::move(address)](int code, const COMMAND_PING::response& rsp, p2p_connection_context& context)
{ {
if(code <= 0) if(code <= 0)
{ {
@ -2398,7 +2390,7 @@ namespace nodetool
network_zone& zone = m_network_zones.at(address.get_zone()); network_zone& zone = m_network_zones.at(address.get_zone());
if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id)
{ {
LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << peerid_to_string(rsp.peer_id)); LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr << ", rsp.peer_id=" << peerid_to_string(rsp.peer_id));
zone.m_net_server.get_config_object().close(ping_context.m_connection_id); zone.m_net_server.get_config_object().close(ping_context.m_connection_id);
return; return;
} }
@ -2434,7 +2426,7 @@ namespace nodetool
COMMAND_REQUEST_SUPPORT_FLAGS::ID, COMMAND_REQUEST_SUPPORT_FLAGS::ID,
support_flags_request, support_flags_request,
m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object(), m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object(),
[=](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_) [f = std::move(f)](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_)
{ {
if(code < 0) if(code < 0)
{ {