diff --git a/common/eda_dde.cpp b/common/eda_dde.cpp index 56ed7a87e1..d75a3b9482 100644 --- a/common/eda_dde.cpp +++ b/common/eda_dde.cpp @@ -22,9 +22,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -/** - * @file eda_dde.cpp - */ +#include +#include +#include +#include #include #include @@ -32,6 +33,8 @@ #include +using namespace std::chrono_literals; + static const wxString HOSTNAME( wxT( "localhost" ) ); // buffer for read and write data in socket connections @@ -121,6 +124,177 @@ void EDA_DRAW_FRAME::OnSockRequestServer( wxSocketEvent& evt ) /* Routines related to the CLIENT */ /**********************************/ +/** + * Spins up a thread to send messages via a socket. No message queuing, if a message is in flight + * when another is posted with Send(), the second is just dropped. + * This is a workaround for "non-blocking" sockets not always being non-blocking, especially on + * Windows. It is kept fairly simple and not exposed to the outside world because it should be + * replaced in a future KiCad version with a real message queue of some sort, and unified with the + * Kiway messaging system. + */ +class ASYNC_SOCKET_HOLDER +{ +public: + ASYNC_SOCKET_HOLDER() : + m_messageReady( false ), + m_shutdown( false ) + { + // Do a dummy Connect so that wx will set up the socket stuff on the main thread, which is + // required even if you later make socket connections on another thread. + wxSocketClient* client = new wxSocketClient; + wxIPV4address addr; + + addr.Hostname( HOSTNAME ); + addr.Service( KICAD_PCB_PORT_SERVICE_NUMBER ); + + client->Connect( addr, false ); + + client->Close(); + client->Destroy(); + + std::thread( &ASYNC_SOCKET_HOLDER::worker, this ).detach(); + } + + ~ASYNC_SOCKET_HOLDER() + { + m_shutdown = true; + m_messageReady = true; + m_cv.notify_one(); + } + + /** + * Attempts to send a message if the thread is available + * @param aService is the port number (i.e. service) to send to + * @param aMessage is the message to send + * @return true if the message was queued + */ + bool Send( int aService, const std::string& aMessage ) + { + if( m_messageReady ) + return false; + + std::lock_guard lock( m_mutex ); + + m_message = std::make_pair( aService, aMessage ); + m_messageReady = true; + m_cv.notify_one(); + + return true; + } + +private: + /** + * Actual task that sends data to the socket server + */ + void worker() + { + while( !m_shutdown ) + { + std::unique_lock lock( m_mutex ); + m_cv.wait( lock, [&]() { return m_messageReady; } ); + + if( m_shutdown ) + return; + + wxSocketClient* sock_client; + bool success = false; + wxIPV4address addr; + + // Create a connexion + addr.Hostname( HOSTNAME ); + addr.Service( m_message.first ); + + // Mini-tutorial for Connect() :-) + // (JP CHARRAS Note: see wxWidgets: sockets/client.cpp sample) + // --------------------------- + // + // There are two ways to use Connect(): blocking and non-blocking, + // depending on the value passed as the 'wait' (2nd) parameter. + // + // Connect(addr, true) will wait until the connection completes, + // returning true on success and false on failure. This call blocks + // the GUI (this might be changed in future releases to honor the + // wxSOCKET_BLOCK flag). + // + // Connect(addr, false) will issue a nonblocking connection request + // and return immediately. If the return value is true, then the + // connection has been already successfully established. If it is + // false, you must wait for the request to complete, either with + // WaitOnConnect() or by watching wxSOCKET_CONNECTION / LOST + // events (please read the documentation). + // + // WaitOnConnect() itself never blocks the GUI (this might change + // in the future to honor the wxSOCKET_BLOCK flag). This call will + // return false on timeout, or true if the connection request + // completes, which in turn might mean: + // + // a) That the connection was successfully established + // b) That the connection request failed (for example, because + // it was refused by the peer. + // + // Use IsConnected() to distinguish between these two. + // + // So, in a brief, you should do one of the following things: + // + // For blocking Connect: + // + // bool success = client->Connect(addr, true); + // + // For nonblocking Connect: + // + // client->Connect(addr, false); + // + // bool waitmore = true; + // while (! client->WaitOnConnect(seconds, millis) && waitmore ) + // { + // // possibly give some feedback to the user, + // // update waitmore if needed. + // } + // bool success = client->IsConnected(); + // + // And that's all :-) + + sock_client = new wxSocketClient( wxSOCKET_BLOCK ); + sock_client->SetTimeout( 2 ); // Time out in Seconds + sock_client->Connect( addr, true ); + + if( sock_client->Ok() && sock_client->IsConnected() ) + { + success = true; + sock_client->SetFlags( wxSOCKET_NOWAIT /*wxSOCKET_WAITALL*/ ); + sock_client->Write( m_message.second.c_str(), m_message.second.length() ); + } + + sock_client->Close(); + sock_client->Destroy(); + + m_messageReady = false; + } + } + + std::pair m_message; + + bool m_messageReady; + + mutable std::mutex m_mutex; + + std::condition_variable m_cv; + + bool m_shutdown; +}; + + +ASYNC_SOCKET_HOLDER* GetSocketHolder() +{ + static std::unique_ptr holder; + + if( !holder ) + holder = std::make_unique(); + + return holder.get(); +} + + /* Used by a client to sent (by a socket connection) a data to a server. * - Open a Socket Client connection * - Send the buffer cmdline @@ -128,79 +302,7 @@ void EDA_DRAW_FRAME::OnSockRequestServer( wxSocketEvent& evt ) * * service is the service number for the TC/IP connection */ -bool SendCommand( int service, const char* cmdline ) +bool SendCommand( int aService, const std::string& aMessage ) { - wxSocketClient* sock_client; - bool success = false; - wxIPV4address addr; - - // Create a connexion - addr.Hostname( HOSTNAME ); - addr.Service( service ); - - // Mini-tutorial for Connect() :-) - // (JP CHARRAS Note: see wxWidgets: sockets/client.cpp sample) - // --------------------------- - // - // There are two ways to use Connect(): blocking and non-blocking, - // depending on the value passed as the 'wait' (2nd) parameter. - // - // Connect(addr, true) will wait until the connection completes, - // returning true on success and false on failure. This call blocks - // the GUI (this might be changed in future releases to honor the - // wxSOCKET_BLOCK flag). - // - // Connect(addr, false) will issue a nonblocking connection request - // and return immediately. If the return value is true, then the - // connection has been already successfully established. If it is - // false, you must wait for the request to complete, either with - // WaitOnConnect() or by watching wxSOCKET_CONNECTION / LOST - // events (please read the documentation). - // - // WaitOnConnect() itself never blocks the GUI (this might change - // in the future to honor the wxSOCKET_BLOCK flag). This call will - // return false on timeout, or true if the connection request - // completes, which in turn might mean: - // - // a) That the connection was successfully established - // b) That the connection request failed (for example, because - // it was refused by the peer. - // - // Use IsConnected() to distinguish between these two. - // - // So, in a brief, you should do one of the following things: - // - // For blocking Connect: - // - // bool success = client->Connect(addr, true); - // - // For nonblocking Connect: - // - // client->Connect(addr, false); - // - // bool waitmore = true; - // while (! client->WaitOnConnect(seconds, millis) && waitmore ) - // { - // // possibly give some feedback to the user, - // // update waitmore if needed. - // } - // bool success = client->IsConnected(); - // - // And that's all :-) - - sock_client = new wxSocketClient(); - sock_client->SetTimeout( 2 ); // Time out in Seconds - sock_client->Connect( addr, false ); - sock_client->WaitOnConnect( 0, 100 ); - - if( sock_client->Ok() && sock_client->IsConnected() ) - { - success = true; - sock_client->SetFlags( wxSOCKET_NOWAIT /*wxSOCKET_WAITALL*/ ); - sock_client->Write( cmdline, strlen( cmdline ) ); - } - - sock_client->Close(); - sock_client->Destroy(); - return success; + return GetSocketHolder()->Send( aService, aMessage ); } diff --git a/cvpcb/cvpcb_mainframe.cpp b/cvpcb/cvpcb_mainframe.cpp index 6da18a0cdf..3006e297b8 100644 --- a/cvpcb/cvpcb_mainframe.cpp +++ b/cvpcb/cvpcb_mainframe.cpp @@ -814,7 +814,7 @@ void CVPCB_MAINFRAME::SendMessageToEESCHEMA( bool aClearHighligntOnly ) std::string packet = "$CLEAR: \"HIGHLIGHTED\""; if( Kiface().IsSingle() ) - SendCommand( MSG_TO_SCH, packet.c_str() ); + SendCommand( MSG_TO_SCH, packet ); else Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this ); @@ -835,7 +835,7 @@ void CVPCB_MAINFRAME::SendMessageToEESCHEMA( bool aClearHighligntOnly ) packet = StrPrintf( "$PART: \"%s\"", TO_UTF8( component->GetReference() ) ); if( Kiface().IsSingle() ) - SendCommand( MSG_TO_SCH, packet.c_str() ); + SendCommand( MSG_TO_SCH, packet ); else Kiway().ExpressMail( FRAME_SCH, MAIL_CROSS_PROBE, packet, this ); } diff --git a/eeschema/cross-probing.cpp b/eeschema/cross-probing.cpp index 0ee1d7e3d4..6d1fe9f9b9 100644 --- a/eeschema/cross-probing.cpp +++ b/eeschema/cross-probing.cpp @@ -449,7 +449,7 @@ void SCH_EDIT_FRAME::SendMessageToPCBNEW( EDA_ITEM* aObjectToSync, SCH_COMPONENT if( !packet.empty() ) { if( Kiface().IsSingle() ) - SendCommand( MSG_TO_PCB, packet.c_str() ); + SendCommand( MSG_TO_PCB, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since @@ -470,7 +470,7 @@ void SCH_EDIT_FRAME::SendCrossProbeNetName( const wxString& aNetName ) if( !packet.empty() ) { if( Kiface().IsSingle() ) - SendCommand( MSG_TO_PCB, packet.c_str() ); + SendCommand( MSG_TO_PCB, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since @@ -521,7 +521,7 @@ void SCH_EDIT_FRAME::SetCrossProbeConnection( const SCH_CONNECTION* aConnection if( !packet.empty() ) { if( Kiface().IsSingle() ) - SendCommand( MSG_TO_PCB, packet.c_str() ); + SendCommand( MSG_TO_PCB, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since @@ -538,7 +538,7 @@ void SCH_EDIT_FRAME::SendCrossProbeClearHighlight() std::string packet = "$CLEAR\n"; if( Kiface().IsSingle() ) - SendCommand( MSG_TO_PCB, packet.c_str() ); + SendCommand( MSG_TO_PCB, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since diff --git a/include/eda_dde.h b/include/eda_dde.h index 3bf77db514..13db4be6f3 100644 --- a/include/eda_dde.h +++ b/include/eda_dde.h @@ -30,6 +30,7 @@ #ifndef EDA_DDE_H_ #define EDA_DDE_H_ +#include #include @@ -45,6 +46,6 @@ #define MSG_TO_PCB KICAD_PCB_PORT_SERVICE_NUMBER #define MSG_TO_SCH KICAD_SCH_PORT_SERVICE_NUMBER -bool SendCommand( int port, const char* cmdline ); +bool SendCommand( int aPort, const std::string& aMessage ); #endif // EDA_DDE_H_ diff --git a/pcbnew/cross-probing.cpp b/pcbnew/cross-probing.cpp index 7d68c7f432..35a35a0261 100644 --- a/pcbnew/cross-probing.cpp +++ b/pcbnew/cross-probing.cpp @@ -469,7 +469,7 @@ void PCB_EDIT_FRAME::SendMessageToEESCHEMA( BOARD_ITEM* aSyncItem ) if( !packet.empty() ) { if( Kiface().IsSingle() ) - SendCommand( MSG_TO_SCH, packet.c_str() ); + SendCommand( MSG_TO_SCH, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since @@ -488,7 +488,7 @@ void PCB_EDIT_FRAME::SendCrossProbeNetName( const wxString& aNetName ) if( !packet.empty() ) { if( Kiface().IsSingle() ) - SendCommand( MSG_TO_SCH, packet.c_str() ); + SendCommand( MSG_TO_SCH, packet ); else { // Typically ExpressMail is going to be s-expression packets, but since