synergy hook DLL will now restart itself if a client tries to

init() it while it's already running.  fixed an uninitialized
pointer bug in CServer and some cleanup-on-error code in
CMSWindowsPrimaryScreen.  also added timeout to read() on
IInputStream and a heartbeat sent by clients so the server
can disconnect clients that are dead but never reset the TCP
connection.  previously the server would keep these dead
clients around forever and if the user was locked on the
client screen for some reason then the server would have to
be rebooted (or the server would have to be killed via a
remote login).
This commit is contained in:
crs 2002-06-26 16:31:48 +00:00
parent d9b2c59d02
commit ed8ed72f26
14 changed files with 169 additions and 63 deletions

View File

@ -13,6 +13,7 @@
#include "CTimerThread.h" #include "CTimerThread.h"
#include "XThread.h" #include "XThread.h"
#include "CLog.h" #include "CLog.h"
#include "CStopwatch.h"
#include "TMethodJob.h" #include "TMethodJob.h"
#include <memory> #include <memory>
@ -301,23 +302,36 @@ CClient::runSession(void*)
m_compressMouse = false; m_compressMouse = false;
// handle messages from server // handle messages from server
CStopwatch heartbeat;
for (;;) { for (;;) {
// if no input is pending then flush compressed mouse motion // if no input is pending then flush compressed mouse motion
if (input->getSize() == 0) { if (input->getSize() == 0) {
flushCompressedMouse(); flushCompressedMouse();
} }
// wait for reply // wait for a message
log((CLOG_DEBUG2 "waiting for message")); log((CLOG_DEBUG2 "waiting for message"));
UInt8 code[4]; UInt8 code[4];
UInt32 n = input->read(code, 4); UInt32 n = input->read(code, 4, kHeartRate);
// verify we got an entire code // check if server hungup
if (n == 0) { if (n == 0) {
log((CLOG_NOTE "server disconnected")); log((CLOG_NOTE "server disconnected"));
// server hungup
break; break;
} }
// check for time out
if (n == (UInt32)-1 || heartbeat.getTime() > kHeartRate) {
// send heartbeat
CProtocolUtil::writef(m_output, kMsgCNoop);
heartbeat.reset();
if (n == (UInt32)-1) {
// no message to process
continue;
}
}
// verify we got an entire code
if (n != 4) { if (n != 4) {
// client sent an incomplete message // client sent an incomplete message
log((CLOG_ERR "incomplete message from server")); log((CLOG_ERR "incomplete message from server"));
@ -347,6 +361,10 @@ CClient::runSession(void*)
else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) {
onKeyRepeat(); onKeyRepeat();
} }
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// accept and discard no-op
continue;
}
else if (memcmp(code, kMsgCEnter, 4) == 0) { else if (memcmp(code, kMsgCEnter, 4) == 0) {
onEnter(); onEnter();
} }

View File

@ -468,7 +468,7 @@ CHTTPProtocol::readLine(IInputStream* stream, CString& tmpBuffer)
// read more from stream // read more from stream
char buffer[4096]; char buffer[4096];
UInt32 n = stream->read(buffer, sizeof(buffer)); UInt32 n = stream->read(buffer, sizeof(buffer), -1.0);
if (n == 0) { if (n == 0) {
// stream is empty. return what's leftover. // stream is empty. return what's leftover.
CString line = tmpBuffer; CString line = tmpBuffer;
@ -514,7 +514,7 @@ CHTTPProtocol::readBlock(IInputStream* stream,
if (n > numBytes) { if (n > numBytes) {
n = numBytes; n = numBytes;
} }
n = stream->read(buffer, n); n = stream->read(buffer, n, -1.0);
// if stream is empty then return what we've got so far // if stream is empty then return what we've got so far
if (n == 0) { if (n == 0) {

View File

@ -2,6 +2,7 @@
#include "CLock.h" #include "CLock.h"
#include "CMutex.h" #include "CMutex.h"
#include "CThread.h" #include "CThread.h"
#include "CStopwatch.h"
#include "IJob.h" #include "IJob.h"
#include "XIO.h" #include "XIO.h"
#include <cstring> #include <cstring>
@ -43,15 +44,19 @@ CBufferedInputStream::hangup()
} }
UInt32 UInt32
CBufferedInputStream::readNoLock(void* dst, UInt32 n) CBufferedInputStream::readNoLock(void* dst, UInt32 n, double timeout)
{ {
if (m_closed) { if (m_closed) {
throw XIOClosed(); throw XIOClosed();
} }
// wait for data (or hangup) // wait for data, hangup, or timeout
CStopwatch timer(true);
while (!m_hungup && m_empty == true) { while (!m_hungup && m_empty == true) {
m_empty.wait(); if (!m_empty.wait(timer, timeout)) {
// timed out
return (UInt32)-1;
}
} }
// read data // read data
@ -98,10 +103,10 @@ CBufferedInputStream::close()
} }
UInt32 UInt32
CBufferedInputStream::read(void* dst, UInt32 n) CBufferedInputStream::read(void* dst, UInt32 n, double timeout)
{ {
CLock lock(m_mutex); CLock lock(m_mutex);
return readNoLock(dst, n); return readNoLock(dst, n, timeout);
} }
UInt32 UInt32

View File

@ -26,7 +26,7 @@ public:
void hangup(); void hangup();
// same as read() but caller must lock the mutex // same as read() but caller must lock the mutex
UInt32 readNoLock(void*, UInt32 count); UInt32 readNoLock(void*, UInt32 count, double timeout);
// accessors // accessors
@ -36,7 +36,7 @@ public:
// IInputStream overrides // IInputStream overrides
// these all lock the mutex for their duration // these all lock the mutex for their duration
virtual void close(); virtual void close();
virtual UInt32 read(void*, UInt32 count); virtual UInt32 read(void*, UInt32 count, double timeout);
virtual UInt32 getSize() const; virtual UInt32 getSize() const;
private: private:

View File

@ -14,7 +14,7 @@ public:
// IInputStream overrides // IInputStream overrides
virtual void close() = 0; virtual void close() = 0;
virtual UInt32 read(void*, UInt32 maxCount) = 0; virtual UInt32 read(void*, UInt32 maxCount, double timeout) = 0;
virtual UInt32 getSize() const = 0; virtual UInt32 getSize() const = 0;
protected: protected:

View File

@ -13,8 +13,11 @@ public:
// read up to maxCount bytes into buffer, return number read. // read up to maxCount bytes into buffer, return number read.
// blocks if no data is currently available. if buffer is NULL // blocks if no data is currently available. if buffer is NULL
// then the data is discarded. // then the data is discarded. returns (UInt32)-1 if there's
virtual UInt32 read(void* buffer, UInt32 maxCount) = 0; // no data for timeout seconds; if timeout < 0 then it blocks
// until data is available.
// (cancellation point)
virtual UInt32 read(void* buffer, UInt32 maxCount, double timeout) = 0;
// accessors // accessors

View File

@ -386,6 +386,7 @@ CMSWindowsPrimaryScreen::onOpenDisplay()
// initialize hook library // initialize hook library
m_init(m_threadID); m_init(m_threadID);
try {
// install the screen saver hook // install the screen saver hook
if (m_installScreenSaver != NULL) { if (m_installScreenSaver != NULL) {
m_installScreenSaver(); m_installScreenSaver();
@ -402,6 +403,11 @@ CMSWindowsPrimaryScreen::onOpenDisplay()
throw XScreenOpenFailure(); throw XScreenOpenFailure();
} }
} }
}
catch (...) {
m_cleanup();
throw;
}
} }
void void

View File

@ -42,6 +42,7 @@ CServer::CServer(const CString& serverName) :
m_active(NULL), m_active(NULL),
m_primaryInfo(NULL), m_primaryInfo(NULL),
m_seqNum(0), m_seqNum(0),
m_activeSaver(NULL),
m_httpServer(NULL), m_httpServer(NULL),
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests) m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests)
{ {

View File

@ -8,6 +8,7 @@
#include "IOutputStream.h" #include "IOutputStream.h"
#include "CThread.h" #include "CThread.h"
#include "CLog.h" #include "CLog.h"
#include "CStopwatch.h"
#include <cstring> #include <cstring>
// //
@ -29,22 +30,35 @@ CServerProtocol1_0::~CServerProtocol1_0()
void void
CServerProtocol1_0::run() CServerProtocol1_0::run()
{ {
// handle messages until the client hangs up // handle messages until the client hangs up or stops sending heartbeats
CStopwatch heartTimer;
for (;;) { for (;;) {
CThread::testCancel(); CThread::testCancel();
// wait for a message // wait for a message
UInt8 code[4]; UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4); UInt32 n = getInputStream()->read(code, 4, kHeartRate);
CThread::testCancel(); CThread::testCancel();
// verify we got an entire code // check if client hungup
if (n == 0) { if (n == 0) {
log((CLOG_NOTE "client \"%s\" disconnected", getClient().c_str())); log((CLOG_NOTE "client \"%s\" disconnected", getClient().c_str()));
// client hungup
return; return;
} }
// check if client has stopped sending heartbeats
if (n == (UInt32)-1) {
if (heartTimer.getTime() > kHeartDeath) {
log((CLOG_NOTE "client \"%s\" is dead", getClient().c_str()));
return;
}
continue;
}
// got a message so reset heartbeat monitor
heartTimer.reset();
// verify we got an entire code
if (n != 4) { if (n != 4) {
log((CLOG_ERR "incomplete message from \"%s\": %d bytes", getClient().c_str(), n)); log((CLOG_ERR "incomplete message from \"%s\": %d bytes", getClient().c_str(), n));
@ -57,6 +71,10 @@ CServerProtocol1_0::run()
if (memcmp(code, kMsgDInfo, 4) == 0) { if (memcmp(code, kMsgDInfo, 4) == 0) {
recvInfo(); recvInfo();
} }
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
continue;
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) { else if (memcmp(code, kMsgCClipboard, 4) == 0) {
recvGrabClipboard(); recvGrabClipboard();
} }
@ -83,8 +101,17 @@ CServerProtocol1_0::queryInfo()
// wait for and verify reply // wait for and verify reply
UInt8 code[4]; UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4); for (;;) {
if (n != 4 && memcmp(code, kMsgDInfo, 4) != 0) { UInt32 n = getInputStream()->read(code, 4, -1.0);
if (n == 4) {
if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard heartbeats
continue;
}
if (memcmp(code, kMsgDInfo, 4) == 0) {
break;
}
}
throw XBadClient(); throw XBadClient();
} }

View File

@ -468,9 +468,13 @@ init(DWORD threadID)
{ {
assert(g_hinstance != NULL); assert(g_hinstance != NULL);
// see if already initialized // see if already initialized. if it is we'll shut down and
// reinitialize. this allows the hook DLL to be reclaimed by
// a new synergyd if the previous one died unexpectedly.
if (g_threadID != 0) { if (g_threadID != 0) {
return 0; uninstallScreenSaver();
uninstall();
cleanup();
} }
// save thread id. we'll post messages to this thread's // save thread id. we'll post messages to this thread's
@ -627,6 +631,7 @@ uninstall(void)
g_keyboard = NULL; g_keyboard = NULL;
g_mouse = NULL; g_mouse = NULL;
g_cbt = NULL; g_cbt = NULL;
g_wheelSupport = kWheelNone;
// show the cursor // show the cursor
restoreCursor(); restoreCursor();
@ -664,7 +669,7 @@ uninstallScreenSaver(void)
assert(g_hinstance != NULL); assert(g_hinstance != NULL);
// uninstall hook unless the mouse wheel hook is installed // uninstall hook unless the mouse wheel hook is installed
if (g_getMessage != NULL && g_threadID == 0) { if (g_getMessage != NULL && g_wheelSupport == kWheelNone) {
UnhookWindowsHookEx(g_getMessage); UnhookWindowsHookEx(g_getMessage);
g_getMessage = NULL; g_getMessage = NULL;
} }

View File

@ -1,5 +1,6 @@
#include "CInputPacketStream.h" #include "CInputPacketStream.h"
#include "CLock.h" #include "CLock.h"
#include "CStopwatch.h"
// //
// CInputPacketStream // CInputPacketStream
@ -26,14 +27,21 @@ CInputPacketStream::close()
} }
UInt32 UInt32
CInputPacketStream::read(void* buffer, UInt32 n) CInputPacketStream::read(void* buffer, UInt32 n, double timeout)
{ {
CLock lock(&m_mutex); CLock lock(&m_mutex);
// wait for entire message to be read. return immediately if // wait for entire message to be read. return if stream
// stream hungup. // hungup or timeout.
if (!waitForFullMessage()) { switch (waitForFullMessage(timeout)) {
case kData:
break;
case kHungup:
return 0; return 0;
case kTimedout:
return (UInt32)-1;
} }
// limit number of bytes to read to the number of bytes left in the // limit number of bytes to read to the number of bytes left in the
@ -43,7 +51,7 @@ CInputPacketStream::read(void* buffer, UInt32 n)
} }
// now read from our buffer // now read from our buffer
n = m_buffer.readNoLock(buffer, n); n = m_buffer.readNoLock(buffer, n, -1.0);
assert(n <= m_size); assert(n <= m_size);
m_size -= n; m_size -= n;
@ -60,48 +68,70 @@ CInputPacketStream::getSize() const
UInt32 UInt32
CInputPacketStream::getSizeNoLock() const CInputPacketStream::getSizeNoLock() const
{ {
CStopwatch timer(true);
while (!hasFullMessage() && getStream()->getSize() > 0) { while (!hasFullMessage() && getStream()->getSize() > 0) {
// read more data // read more data
if (!getMoreMessage()) { if (getMoreMessage(-1.0) != kData) {
// stream hungup // stream hungup
return false; return 0;
} }
} }
return m_size; return m_size;
} }
bool CInputPacketStream::EResult
CInputPacketStream::waitForFullMessage() const CInputPacketStream::waitForFullMessage(double timeout) const
{ {
CStopwatch timer(true);
while (!hasFullMessage()) { while (!hasFullMessage()) {
// compute remaining timeout
double t = timeout - timer.getTime();
if (timeout >= 0.0 && t <= 0.0) {
// timeout
return kTimedout;
}
// read more data // read more data
if (!getMoreMessage()) { switch (getMoreMessage(t)) {
case kData:
break;
case kHungup:
// stream hungup // stream hungup
return false; return kHungup;
case kTimedout:
// stream timed out
return kTimedout;
} }
} }
return true; return kData;
} }
bool CInputPacketStream::EResult
CInputPacketStream::getMoreMessage() const CInputPacketStream::getMoreMessage(double timeout) const
{ {
// read more data // read more data
char buffer[4096]; char buffer[4096];
UInt32 n = getStream()->read(buffer, sizeof(buffer)); UInt32 n = getStream()->read(buffer, sizeof(buffer), timeout);
// return if stream timed out
if (n == (UInt32)-1) {
return kTimedout;
}
// return if stream hungup // return if stream hungup
if (n == 0) { if (n == 0) {
m_buffer.hangup(); m_buffer.hangup();
return false; return kHungup;
} }
// append to our buffer // append to our buffer
m_buffer.write(buffer, n); m_buffer.write(buffer, n);
return true; return kData;
} }
bool bool
@ -117,7 +147,7 @@ CInputPacketStream::hasFullMessage() const
// save payload length // save payload length
UInt8 buffer[4]; UInt8 buffer[4];
UInt32 n = m_buffer.readNoLock(buffer, sizeof(buffer)); UInt32 n = m_buffer.readNoLock(buffer, sizeof(buffer), -1.0);
assert(n == 4); assert(n == 4);
m_size = ((UInt32)buffer[0] << 24) | m_size = ((UInt32)buffer[0] << 24) |
((UInt32)buffer[1] << 16) | ((UInt32)buffer[1] << 16) |

View File

@ -16,13 +16,15 @@ public:
// IInputStream overrides // IInputStream overrides
virtual void close(); virtual void close();
virtual UInt32 read(void*, UInt32 maxCount); virtual UInt32 read(void*, UInt32 maxCount, double timeout);
virtual UInt32 getSize() const; virtual UInt32 getSize() const;
private: private:
enum EResult { kData, kHungup, kTimedout };
UInt32 getSizeNoLock() const; UInt32 getSizeNoLock() const;
bool waitForFullMessage() const; EResult waitForFullMessage(double timeout) const;
bool getMoreMessage() const; EResult getMoreMessage(double timeout) const;
bool hasFullMessage() const; bool hasFullMessage() const;
private: private:

View File

@ -348,7 +348,7 @@ CProtocolUtil::read(IInputStream* stream, void* vbuffer, UInt32 count)
UInt8* buffer = reinterpret_cast<UInt8*>(vbuffer); UInt8* buffer = reinterpret_cast<UInt8*>(vbuffer);
while (count > 0) { while (count > 0) {
// read more // read more
UInt32 n = stream->read(buffer, count); UInt32 n = stream->read(buffer, count, -1.0);
// bail if stream has hungup // bail if stream has hungup
if (n == 0) { if (n == 0) {

View File

@ -10,6 +10,12 @@ static const SInt16 kProtocolMinorVersion = 1;
// contact port number // contact port number
static const UInt16 kDefaultPort = 24800; static const UInt16 kDefaultPort = 24800;
// time between heartbeats (in seconds)
static const double kHeartRate = 2.0;
// time without a heartbeat that we call death
static const double kHeartDeath = 3.0 * kHeartRate;
// //
// message codes (trailing NUL is not part of code). in comments, $n // message codes (trailing NUL is not part of code). in comments, $n
// refers to the n'th argument (counting from one). message codes are // refers to the n'th argument (counting from one). message codes are
@ -24,6 +30,9 @@ static const UInt16 kDefaultPort = 24800;
// command codes // command codes
// //
// no operation; secondary -> primary
static const char kMsgCNoop[] = "CNOP";
// close connection; primary -> secondary // close connection; primary -> secondary
static const char kMsgCClose[] = "CBYE"; static const char kMsgCClose[] = "CBYE";