diff --git a/plugin-unix.diff b/plugin-unix.diff new file mode 100644 index 00000000..e6e39633 --- /dev/null +++ b/plugin-unix.diff @@ -0,0 +1,15 @@ +diff --git a/src/lib/arch/unix/ArchPluginUnix.cpp b/src/lib/arch/unix/ArchPluginUnix.cpp +index 997c274..3e390f0 100644 +--- a/src/lib/arch/unix/ArchPluginUnix.cpp ++++ b/src/lib/arch/unix/ArchPluginUnix.cpp +@@ -76,8 +76,8 @@ ArchPluginUnix::load() + void* library = dlopen(path.c_str(), RTLD_LAZY); + + if (library == NULL) { +- LOG((CLOG_ERR "failed to load plugin: %s", (*it).c_str())); +- throw XArch(dlerror()); ++ LOG((CLOG_ERR "failed to load plugin '%s', error: %s", (*it).c_str(), dlerror())); ++ continue; + } + + String filename = synergy::string::removeFileExt(*it); diff --git a/src/lib/arch/win32/ArchMiscWindows.cpp b/src/lib/arch/win32/ArchMiscWindows.cpp index 4171f1c4..92aad1a4 100644 --- a/src/lib/arch/win32/ArchMiscWindows.cpp +++ b/src/lib/arch/win32/ArchMiscWindows.cpp @@ -60,6 +60,9 @@ ArchMiscWindows::cleanup() void ArchMiscWindows::init() { + // stop windows system error dialogs from showing. + SetErrorMode(SEM_FAILCRITICALERRORS); + s_dialogs = new Dialogs; } diff --git a/src/lib/arch/win32/ArchPluginWindows.cpp b/src/lib/arch/win32/ArchPluginWindows.cpp index 2a223746..ce814cf5 100644 --- a/src/lib/arch/win32/ArchPluginWindows.cpp +++ b/src/lib/arch/win32/ArchPluginWindows.cpp @@ -60,8 +60,9 @@ ArchPluginWindows::load() HINSTANCE library = LoadLibrary(path.c_str()); if (library == NULL) { - LOG((CLOG_ERR "failed to load plugin: %s %d", (*it).c_str(), GetLastError())); - throw XArch(new XArchEvalWindows); + String error = XArchEvalWindows().eval(); + LOG((CLOG_ERR "failed to load plugin '%s', error: %s", (*it).c_str(), error.c_str())); + continue; } void* lib = reinterpret_cast(library); diff --git a/src/lib/base/log_outputters.cpp b/src/lib/base/log_outputters.cpp index 8db32f3a..010d79f2 100644 --- a/src/lib/base/log_outputters.cpp +++ b/src/lib/base/log_outputters.cpp @@ -22,6 +22,10 @@ #include +enum EFileLogOutputter { + kFileSizeLimit = 1024 // kb +}; + // // StopLogOutputter // @@ -252,13 +256,27 @@ FileLogOutputter::setLogFilename(const char* logFile) bool FileLogOutputter::write(ELevel level, const char *message) { + bool moveFile = false; + std::ofstream m_handle; m_handle.open(m_fileName.c_str(), std::fstream::app); if (m_handle.is_open() && m_handle.fail() != true) { m_handle << message << std::endl; + + // when file size exceeds limits, move to 'old log' filename. + int p = m_handle.tellp(); + if (p > (kFileSizeLimit * 1024)) { + moveFile = true; + } } m_handle.close(); + if (moveFile) { + String oldLogFilename = synergy::string::sprintf("%s.1", m_fileName.c_str()); + remove(oldLogFilename.c_str()); + rename(m_fileName.c_str(), oldLogFilename.c_str()); + } + return true; } diff --git a/src/lib/ipc/IpcLogOutputter.cpp b/src/lib/ipc/IpcLogOutputter.cpp index 6e4411ca..7474f655 100644 --- a/src/lib/ipc/IpcLogOutputter.cpp +++ b/src/lib/ipc/IpcLogOutputter.cpp @@ -30,17 +30,28 @@ #include "base/TMethodEventJob.h" #include "base/TMethodJob.h" -// limit number of log lines sent in one message. -#define MAX_SEND 100 +enum EIpcLogOutputter { + kBufferMaxSize = 1000, + kMaxSendLines = 100, + kBufferRateWriteLimit = 1000, // writes per kBufferRateTime + kBufferRateTimeLimit = 1 // seconds +}; IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer) : -m_ipcServer(ipcServer), -m_bufferMutex(ARCH->newMutex()), -m_sending(false), -m_running(true), -m_notifyCond(ARCH->newCondVar()), -m_notifyMutex(ARCH->newMutex()), -m_bufferWaiting(false) + m_ipcServer(ipcServer), + m_bufferMutex(ARCH->newMutex()), + m_sending(false), + m_running(true), + m_notifyCond(ARCH->newCondVar()), + m_notifyMutex(ARCH->newMutex()), + m_bufferWaiting(false), + m_bufferMaxSize(kBufferMaxSize), + m_bufferEmptyCond(ARCH->newCondVar()), + m_bufferEmptyMutex(ARCH->newMutex()), + m_bufferRateWriteLimit(kBufferRateWriteLimit), + m_bufferRateTimeLimit(kBufferRateTimeLimit), + m_bufferWriteCount(0), + m_bufferRateStart(ARCH->time()) { m_bufferThread = new Thread(new TMethodJob( this, &IpcLogOutputter::bufferThread)); @@ -48,15 +59,20 @@ m_bufferWaiting(false) IpcLogOutputter::~IpcLogOutputter() { - m_running = false; - notifyBuffer(); - m_bufferThread->wait(5); + close(); ARCH->closeMutex(m_bufferMutex); delete m_bufferThread; ARCH->closeCondVar(m_notifyCond); ARCH->closeMutex(m_notifyMutex); + + ARCH->closeCondVar(m_bufferEmptyCond); + +#ifndef WINAPI_CARBON + // HACK: assert fails on mac debug, can't see why. + ARCH->closeMutex(m_bufferEmptyMutex); +#endif // WINAPI_CARBON } void @@ -67,6 +83,9 @@ IpcLogOutputter::open(const char* title) void IpcLogOutputter::close() { + m_running = false; + notifyBuffer(); + m_bufferThread->wait(5); } void @@ -107,7 +126,27 @@ void IpcLogOutputter::appendBuffer(const String& text) { ArchMutexLock lock(m_bufferMutex); - m_buffer.push(text); + + double elapsed = ARCH->time() - m_bufferRateStart; + if (elapsed < m_bufferRateTimeLimit) { + if (m_bufferWriteCount >= m_bufferRateWriteLimit) { + // discard the log line if we've logged too much. + return; + } + } + else { + m_bufferWriteCount = 0; + m_bufferRateStart = ARCH->time(); + } + + if (m_buffer.size() >= m_bufferMaxSize) { + // if the queue is exceeds size limit, + // throw away the oldest item + m_buffer.pop_front(); + } + + m_buffer.push_back(text); + m_bufferWriteCount++; } void @@ -133,6 +172,11 @@ IpcLogOutputter::bufferThread(void*) break; } + if (m_buffer.empty()) { + ArchMutexLock lock(m_bufferEmptyMutex); + ARCH->broadcastCondVar(m_bufferEmptyCond); + } + m_bufferWaiting = true; ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1); m_bufferWaiting = false; @@ -168,7 +212,7 @@ IpcLogOutputter::getChunk(size_t count) for (size_t i = 0; i < count; i++) { chunk.append(m_buffer.front()); chunk.append("\n"); - m_buffer.pop(); + m_buffer.pop_front(); } return chunk; } @@ -176,9 +220,34 @@ IpcLogOutputter::getChunk(size_t count) void IpcLogOutputter::sendBuffer() { - IpcLogLineMessage message(getChunk(MAX_SEND)); + IpcLogLineMessage message(getChunk(kMaxSendLines)); m_sending = true; m_ipcServer.send(message, kIpcClientGui); m_sending = false; } + +void +IpcLogOutputter::bufferMaxSize(UInt16 bufferMaxSize) +{ + m_bufferMaxSize = bufferMaxSize; +} + +UInt16 +IpcLogOutputter::bufferMaxSize() const +{ + return m_bufferMaxSize; +} + +void +IpcLogOutputter::waitForEmpty() +{ + ARCH->waitCondVar(m_bufferEmptyCond, m_bufferEmptyMutex, -1); +} + +void +IpcLogOutputter::bufferRateLimit(UInt16 writeLimit, double timeLimit) +{ + m_bufferRateWriteLimit = writeLimit; + m_bufferRateTimeLimit = timeLimit; +} diff --git a/src/lib/ipc/IpcLogOutputter.h b/src/lib/ipc/IpcLogOutputter.h index 9871095d..9b277a03 100644 --- a/src/lib/ipc/IpcLogOutputter.h +++ b/src/lib/ipc/IpcLogOutputter.h @@ -22,7 +22,7 @@ #include "arch/IArchMultithread.h" #include "base/ILogOutputter.h" -#include +#include class IpcServer; class Event; @@ -42,6 +42,9 @@ public: virtual void close(); virtual void show(bool showIfEmpty); virtual bool write(ELevel level, const char* message); + + //! @name manipulators + //@{ //! Same as write, but allows message to sidestep anti-recursion mechanism. bool write(ELevel level, const char* text, bool force); @@ -49,24 +52,64 @@ public: //! Notify that the buffer should be sent. void notifyBuffer(); + //! Set the buffer size. + /*! + Set the maximum size of the buffer to protect memory + from runaway logging. + */ + void bufferMaxSize(UInt16 bufferMaxSize); + + //! Wait for empty buffer + /*! + Wait on a cond var until the buffer is empty. + */ + void waitForEmpty(); + + //! Set the buffer size. + /*! + Set the maximum number of \p writeRate for every \p timeRate in seconds. + */ + void bufferRateLimit(UInt16 writeLimit, double timeLimit); + + //@} + + //! @name accessors + //@{ + + //! Get the buffer size + /*! + Returns the maximum size of the buffer. + */ + UInt16 bufferMaxSize() const; + + //@} + private: + void init(); void bufferThread(void*); String getChunk(size_t count); void sendBuffer(); void appendBuffer(const String& text); private: - typedef std::queue Buffer; + typedef std::deque Buffer; IpcServer& m_ipcServer; Buffer m_buffer; ArchMutex m_bufferMutex; bool m_sending; - Thread* m_bufferThread; + Thread* m_bufferThread; bool m_running; ArchCond m_notifyCond; ArchMutex m_notifyMutex; bool m_bufferWaiting; IArchMultithread::ThreadID m_bufferThreadId; + UInt16 m_bufferMaxSize; + ArchCond m_bufferEmptyCond; + ArchMutex m_bufferEmptyMutex; + UInt16 m_bufferRateWriteLimit; + double m_bufferRateTimeLimit; + UInt16 m_bufferWriteCount; + double m_bufferRateStart; }; diff --git a/src/lib/ipc/IpcServer.cpp b/src/lib/ipc/IpcServer.cpp index 618e7df2..ce533107 100644 --- a/src/lib/ipc/IpcServer.cpp +++ b/src/lib/ipc/IpcServer.cpp @@ -33,17 +33,20 @@ // IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : - m_socket(events, socketMultiplexer), - m_address(NetworkAddress(IPC_HOST, IPC_PORT)), - m_events(events) + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_socket(nullptr), + m_address(NetworkAddress(IPC_HOST, IPC_PORT)) { init(); } IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : - m_socket(events, socketMultiplexer), - m_address(NetworkAddress(IPC_HOST, port)), - m_events(events) + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_address(NetworkAddress(IPC_HOST, port)) { init(); } @@ -51,17 +54,27 @@ IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, void IpcServer::init() { + m_socket = new TCPListenSocket(m_events, m_socketMultiplexer); + m_clientsMutex = ARCH->newMutex(); m_address.resolve(); m_events->adoptHandler( - m_events->forIListenSocket().connecting(), &m_socket, + m_events->forIListenSocket().connecting(), m_socket, new TMethodEventJob( this, &IpcServer::handleClientConnecting)); } IpcServer::~IpcServer() { + if (m_mock) { + return; + } + + if (m_socket != nullptr) { + delete m_socket; + } + ARCH->lockMutex(m_clientsMutex); ClientList::iterator it; for (it = m_clients.begin(); it != m_clients.end(); it++) { @@ -71,19 +84,19 @@ IpcServer::~IpcServer() ARCH->unlockMutex(m_clientsMutex); ARCH->closeMutex(m_clientsMutex); - m_events->removeHandler(m_events->forIListenSocket().connecting(), &m_socket); + m_events->removeHandler(m_events->forIListenSocket().connecting(), m_socket); } void IpcServer::listen() { - m_socket.bind(m_address); + m_socket->bind(m_address); } void IpcServer::handleClientConnecting(const Event&, void*) { - synergy::IStream* stream = m_socket.accept(); + synergy::IStream* stream = m_socket->accept(); if (stream == NULL) { return; } diff --git a/src/lib/ipc/IpcServer.h b/src/lib/ipc/IpcServer.h index b16b74f0..2c442307 100644 --- a/src/lib/ipc/IpcServer.h +++ b/src/lib/ipc/IpcServer.h @@ -49,17 +49,17 @@ public: //@{ //! Opens a TCP socket only allowing local connections. - void listen(); + virtual void listen(); //! Send a message to all clients matching the filter type. - void send(const IpcMessage& message, EIpcClientType filterType); + virtual void send(const IpcMessage& message, EIpcClientType filterType); //@} //! @name accessors //@{ //! Returns true when there are clients of the specified type connected. - bool hasClients(EIpcClientType clientType) const; + virtual bool hasClients(EIpcClientType clientType) const; //@} @@ -72,10 +72,21 @@ private: private: typedef std::list ClientList; - - TCPListenSocket m_socket; + + bool m_mock; + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; + TCPListenSocket* m_socket; NetworkAddress m_address; ClientList m_clients; ArchMutex m_clientsMutex; - IEventQueue* m_events; + +#ifdef TEST_ENV +public: + IpcServer() : + m_mock(true), + m_events(nullptr), + m_socketMultiplexer(nullptr), + m_socket(nullptr) { } +#endif }; diff --git a/src/lib/synergy/ToolApp.cpp b/src/lib/synergy/ToolApp.cpp index f31e2fd4..c9a2b5c7 100644 --- a/src/lib/synergy/ToolApp.cpp +++ b/src/lib/synergy/ToolApp.cpp @@ -63,6 +63,7 @@ ToolApp::run(int argc, char** argv) return kExitFailed; } else { + // HACK: send to standard out so watchdog can parse. std::cout << "activeDesktop:" << name.c_str() << std::endl; } #endif diff --git a/src/test/mock/ipc/MockIpcServer.h b/src/test/mock/ipc/MockIpcServer.h new file mode 100644 index 00000000..ae63cd34 --- /dev/null +++ b/src/test/mock/ipc/MockIpcServer.h @@ -0,0 +1,35 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Si Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ipc/IpcServer.h" +#include "ipc/IpcMessage.h" + +#include "test/global/gmock.h" + +class IEventQueue; + +class MockIpcServer : public IpcServer +{ +public: + MockIpcServer() { } + + MOCK_METHOD0(listen, void()); + MOCK_METHOD2(send, void(const IpcMessage&, EIpcClientType)); + MOCK_CONST_METHOD1(hasClients, bool(EIpcClientType)); +}; diff --git a/src/test/unittests/ipc/IpcLogOutputterTests.cpp b/src/test/unittests/ipc/IpcLogOutputterTests.cpp new file mode 100644 index 00000000..18f1eb21 --- /dev/null +++ b/src/test/unittests/ipc/IpcLogOutputterTests.cpp @@ -0,0 +1,95 @@ +/* + * synergy -- mouse and keyboard sharing utility + * Copyright (C) 2015 Synergy Si Ltd. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file LICENSE that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define TEST_ENV + +#include "test/mock/ipc/MockIpcServer.h" + +#include "mt/Thread.h" +#include "ipc/IpcLogOutputter.h" +#include "base/String.h" +#include "common/common.h" + +#include "test/global/gmock.h" +#include "test/global/gtest.h" + +// HACK: ipc logging only used on windows anyway +#if WINAPI_MSWINDOWS + +using ::testing::_; +using ::testing::Return; +using ::testing::Matcher; +using ::testing::MatcherCast; +using ::testing::Property; +using ::testing::StrEq; + +using namespace synergy; + +inline const Matcher IpcLogLineMessageEq(const String& s) { + const Matcher m( + Property(&IpcLogLineMessage::logLine, StrEq(s))); + return MatcherCast(m); +} + +TEST(IpcLogOutputterTests, write_bufferSizeWrapping) +{ + MockIpcServer mockServer; + + ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true)); + + EXPECT_CALL(mockServer, hasClients(_)).Times(1); + EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 2\nmock 3\n"), _)).Times(1); + + IpcLogOutputter outputter(mockServer); + outputter.bufferMaxSize(2); + + // log more lines than the buffer can contain + outputter.write(kNOTE, "mock 1"); + outputter.write(kNOTE, "mock 2"); + outputter.write(kNOTE, "mock 3"); + + // wait for the buffer to be empty (all lines sent to IPC) + outputter.waitForEmpty(); +} + +TEST(IpcLogOutputterTests, write_bufferRateLimit) +{ + MockIpcServer mockServer; + + ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true)); + + EXPECT_CALL(mockServer, hasClients(_)).Times(2); + EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\n"), _)).Times(1); + EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 3\n"), _)).Times(1); + + IpcLogOutputter outputter(mockServer); + outputter.bufferRateLimit(1, 0.001); // 1ms + + // log 1 more line than the buffer can accept in time limit. + outputter.write(kNOTE, "mock 1"); + outputter.write(kNOTE, "mock 2"); + outputter.waitForEmpty(); + + // after waiting the time limit send another to make sure + // we can log after the time limit passes. + ARCH->sleep(0.01); // 10ms + outputter.write(kNOTE, "mock 3"); + outputter.write(kNOTE, "mock 4"); + outputter.waitForEmpty(); +} + +#endif // WINAPI_MSWINDOWS