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 "XThread.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "TMethodJob.h"
#include <memory>
@ -301,23 +302,36 @@ CClient::runSession(void*)
m_compressMouse = false;
// handle messages from server
CStopwatch heartbeat;
for (;;) {
// if no input is pending then flush compressed mouse motion
if (input->getSize() == 0) {
flushCompressedMouse();
}
// wait for reply
// wait for a message
log((CLOG_DEBUG2 "waiting for message"));
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) {
log((CLOG_NOTE "server disconnected"));
// server hungup
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) {
// client sent an incomplete message
log((CLOG_ERR "incomplete message from server"));
@ -347,6 +361,10 @@ CClient::runSession(void*)
else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) {
onKeyRepeat();
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// accept and discard no-op
continue;
}
else if (memcmp(code, kMsgCEnter, 4) == 0) {
onEnter();
}

View File

@ -468,7 +468,7 @@ CHTTPProtocol::readLine(IInputStream* stream, CString& tmpBuffer)
// read more from stream
char buffer[4096];
UInt32 n = stream->read(buffer, sizeof(buffer));
UInt32 n = stream->read(buffer, sizeof(buffer), -1.0);
if (n == 0) {
// stream is empty. return what's leftover.
CString line = tmpBuffer;
@ -514,7 +514,7 @@ CHTTPProtocol::readBlock(IInputStream* stream,
if (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 (n == 0) {

View File

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

View File

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

View File

@ -14,7 +14,7 @@ public:
// IInputStream overrides
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;
protected:

View File

@ -13,8 +13,11 @@ public:
// read up to maxCount bytes into buffer, return number read.
// blocks if no data is currently available. if buffer is NULL
// then the data is discarded.
virtual UInt32 read(void* buffer, UInt32 maxCount) = 0;
// then the data is discarded. returns (UInt32)-1 if there's
// 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

View File

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

View File

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

View File

@ -8,6 +8,7 @@
#include "IOutputStream.h"
#include "CThread.h"
#include "CLog.h"
#include "CStopwatch.h"
#include <cstring>
//
@ -29,22 +30,35 @@ CServerProtocol1_0::~CServerProtocol1_0()
void
CServerProtocol1_0::run()
{
// handle messages until the client hangs up
// handle messages until the client hangs up or stops sending heartbeats
CStopwatch heartTimer;
for (;;) {
CThread::testCancel();
// wait for a message
UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4);
UInt32 n = getInputStream()->read(code, 4, kHeartRate);
CThread::testCancel();
// verify we got an entire code
// check if client hungup
if (n == 0) {
log((CLOG_NOTE "client \"%s\" disconnected", getClient().c_str()));
// client hungup
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) {
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) {
recvInfo();
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// discard no-ops
continue;
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) {
recvGrabClipboard();
}
@ -83,8 +101,17 @@ CServerProtocol1_0::queryInfo()
// wait for and verify reply
UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4);
if (n != 4 && memcmp(code, kMsgDInfo, 4) != 0) {
for (;;) {
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();
}

View File

@ -468,9 +468,13 @@ init(DWORD threadID)
{
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) {
return 0;
uninstallScreenSaver();
uninstall();
cleanup();
}
// save thread id. we'll post messages to this thread's
@ -627,6 +631,7 @@ uninstall(void)
g_keyboard = NULL;
g_mouse = NULL;
g_cbt = NULL;
g_wheelSupport = kWheelNone;
// show the cursor
restoreCursor();
@ -664,7 +669,7 @@ uninstallScreenSaver(void)
assert(g_hinstance != NULL);
// 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);
g_getMessage = NULL;
}

View File

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

View File

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

View File

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

View File

@ -10,6 +10,12 @@ static const SInt16 kProtocolMinorVersion = 1;
// contact port number
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
// refers to the n'th argument (counting from one). message codes are
@ -24,6 +30,9 @@ static const UInt16 kDefaultPort = 24800;
// command codes
//
// no operation; secondary -> primary
static const char kMsgCNoop[] = "CNOP";
// close connection; primary -> secondary
static const char kMsgCClose[] = "CBYE";