Merge remote-tracking branch 'origin/master' into jerry-sandbox

This commit is contained in:
Jerry (Xinyu Hou) 2015-05-19 11:37:45 -07:00
commit 00ceda55dc
11 changed files with 340 additions and 36 deletions

15
plugin-unix.diff Normal file
View File

@ -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);

View File

@ -60,6 +60,9 @@ ArchMiscWindows::cleanup()
void void
ArchMiscWindows::init() ArchMiscWindows::init()
{ {
// stop windows system error dialogs from showing.
SetErrorMode(SEM_FAILCRITICALERRORS);
s_dialogs = new Dialogs; s_dialogs = new Dialogs;
} }

View File

@ -60,8 +60,9 @@ ArchPluginWindows::load()
HINSTANCE library = LoadLibrary(path.c_str()); HINSTANCE library = LoadLibrary(path.c_str());
if (library == NULL) { if (library == NULL) {
LOG((CLOG_ERR "failed to load plugin: %s %d", (*it).c_str(), GetLastError())); String error = XArchEvalWindows().eval();
throw XArch(new XArchEvalWindows); LOG((CLOG_ERR "failed to load plugin '%s', error: %s", (*it).c_str(), error.c_str()));
continue;
} }
void* lib = reinterpret_cast<void*>(library); void* lib = reinterpret_cast<void*>(library);

View File

@ -22,6 +22,10 @@
#include <fstream> #include <fstream>
enum EFileLogOutputter {
kFileSizeLimit = 1024 // kb
};
// //
// StopLogOutputter // StopLogOutputter
// //
@ -252,13 +256,27 @@ FileLogOutputter::setLogFilename(const char* logFile)
bool bool
FileLogOutputter::write(ELevel level, const char *message) FileLogOutputter::write(ELevel level, const char *message)
{ {
bool moveFile = false;
std::ofstream m_handle; std::ofstream m_handle;
m_handle.open(m_fileName.c_str(), std::fstream::app); m_handle.open(m_fileName.c_str(), std::fstream::app);
if (m_handle.is_open() && m_handle.fail() != true) { if (m_handle.is_open() && m_handle.fail() != true) {
m_handle << message << std::endl; 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(); 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; return true;
} }

View File

@ -30,17 +30,28 @@
#include "base/TMethodEventJob.h" #include "base/TMethodEventJob.h"
#include "base/TMethodJob.h" #include "base/TMethodJob.h"
// limit number of log lines sent in one message. enum EIpcLogOutputter {
#define MAX_SEND 100 kBufferMaxSize = 1000,
kMaxSendLines = 100,
kBufferRateWriteLimit = 1000, // writes per kBufferRateTime
kBufferRateTimeLimit = 1 // seconds
};
IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer) : IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer) :
m_ipcServer(ipcServer), m_ipcServer(ipcServer),
m_bufferMutex(ARCH->newMutex()), m_bufferMutex(ARCH->newMutex()),
m_sending(false), m_sending(false),
m_running(true), m_running(true),
m_notifyCond(ARCH->newCondVar()), m_notifyCond(ARCH->newCondVar()),
m_notifyMutex(ARCH->newMutex()), m_notifyMutex(ARCH->newMutex()),
m_bufferWaiting(false) 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<IpcLogOutputter>( m_bufferThread = new Thread(new TMethodJob<IpcLogOutputter>(
this, &IpcLogOutputter::bufferThread)); this, &IpcLogOutputter::bufferThread));
@ -48,15 +59,20 @@ m_bufferWaiting(false)
IpcLogOutputter::~IpcLogOutputter() IpcLogOutputter::~IpcLogOutputter()
{ {
m_running = false; close();
notifyBuffer();
m_bufferThread->wait(5);
ARCH->closeMutex(m_bufferMutex); ARCH->closeMutex(m_bufferMutex);
delete m_bufferThread; delete m_bufferThread;
ARCH->closeCondVar(m_notifyCond); ARCH->closeCondVar(m_notifyCond);
ARCH->closeMutex(m_notifyMutex); 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 void
@ -67,6 +83,9 @@ IpcLogOutputter::open(const char* title)
void void
IpcLogOutputter::close() IpcLogOutputter::close()
{ {
m_running = false;
notifyBuffer();
m_bufferThread->wait(5);
} }
void void
@ -107,7 +126,27 @@ void
IpcLogOutputter::appendBuffer(const String& text) IpcLogOutputter::appendBuffer(const String& text)
{ {
ArchMutexLock lock(m_bufferMutex); 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 void
@ -133,6 +172,11 @@ IpcLogOutputter::bufferThread(void*)
break; break;
} }
if (m_buffer.empty()) {
ArchMutexLock lock(m_bufferEmptyMutex);
ARCH->broadcastCondVar(m_bufferEmptyCond);
}
m_bufferWaiting = true; m_bufferWaiting = true;
ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1); ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1);
m_bufferWaiting = false; m_bufferWaiting = false;
@ -168,7 +212,7 @@ IpcLogOutputter::getChunk(size_t count)
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
chunk.append(m_buffer.front()); chunk.append(m_buffer.front());
chunk.append("\n"); chunk.append("\n");
m_buffer.pop(); m_buffer.pop_front();
} }
return chunk; return chunk;
} }
@ -176,9 +220,34 @@ IpcLogOutputter::getChunk(size_t count)
void void
IpcLogOutputter::sendBuffer() IpcLogOutputter::sendBuffer()
{ {
IpcLogLineMessage message(getChunk(MAX_SEND)); IpcLogLineMessage message(getChunk(kMaxSendLines));
m_sending = true; m_sending = true;
m_ipcServer.send(message, kIpcClientGui); m_ipcServer.send(message, kIpcClientGui);
m_sending = false; 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;
}

View File

@ -22,7 +22,7 @@
#include "arch/IArchMultithread.h" #include "arch/IArchMultithread.h"
#include "base/ILogOutputter.h" #include "base/ILogOutputter.h"
#include <queue> #include <deque>
class IpcServer; class IpcServer;
class Event; class Event;
@ -42,6 +42,9 @@ public:
virtual void close(); virtual void close();
virtual void show(bool showIfEmpty); virtual void show(bool showIfEmpty);
virtual bool write(ELevel level, const char* message); virtual bool write(ELevel level, const char* message);
//! @name manipulators
//@{
//! Same as write, but allows message to sidestep anti-recursion mechanism. //! Same as write, but allows message to sidestep anti-recursion mechanism.
bool write(ELevel level, const char* text, bool force); bool write(ELevel level, const char* text, bool force);
@ -49,24 +52,64 @@ public:
//! Notify that the buffer should be sent. //! Notify that the buffer should be sent.
void notifyBuffer(); 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: private:
void init();
void bufferThread(void*); void bufferThread(void*);
String getChunk(size_t count); String getChunk(size_t count);
void sendBuffer(); void sendBuffer();
void appendBuffer(const String& text); void appendBuffer(const String& text);
private: private:
typedef std::queue<String> Buffer; typedef std::deque<String> Buffer;
IpcServer& m_ipcServer; IpcServer& m_ipcServer;
Buffer m_buffer; Buffer m_buffer;
ArchMutex m_bufferMutex; ArchMutex m_bufferMutex;
bool m_sending; bool m_sending;
Thread* m_bufferThread; Thread* m_bufferThread;
bool m_running; bool m_running;
ArchCond m_notifyCond; ArchCond m_notifyCond;
ArchMutex m_notifyMutex; ArchMutex m_notifyMutex;
bool m_bufferWaiting; bool m_bufferWaiting;
IArchMultithread::ThreadID IArchMultithread::ThreadID
m_bufferThreadId; m_bufferThreadId;
UInt16 m_bufferMaxSize;
ArchCond m_bufferEmptyCond;
ArchMutex m_bufferEmptyMutex;
UInt16 m_bufferRateWriteLimit;
double m_bufferRateTimeLimit;
UInt16 m_bufferWriteCount;
double m_bufferRateStart;
}; };

View File

@ -33,17 +33,20 @@
// //
IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
m_socket(events, socketMultiplexer), m_mock(false),
m_address(NetworkAddress(IPC_HOST, IPC_PORT)), m_events(events),
m_events(events) m_socketMultiplexer(socketMultiplexer),
m_socket(nullptr),
m_address(NetworkAddress(IPC_HOST, IPC_PORT))
{ {
init(); init();
} }
IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) :
m_socket(events, socketMultiplexer), m_mock(false),
m_address(NetworkAddress(IPC_HOST, port)), m_events(events),
m_events(events) m_socketMultiplexer(socketMultiplexer),
m_address(NetworkAddress(IPC_HOST, port))
{ {
init(); init();
} }
@ -51,17 +54,27 @@ IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer,
void void
IpcServer::init() IpcServer::init()
{ {
m_socket = new TCPListenSocket(m_events, m_socketMultiplexer);
m_clientsMutex = ARCH->newMutex(); m_clientsMutex = ARCH->newMutex();
m_address.resolve(); m_address.resolve();
m_events->adoptHandler( m_events->adoptHandler(
m_events->forIListenSocket().connecting(), &m_socket, m_events->forIListenSocket().connecting(), m_socket,
new TMethodEventJob<IpcServer>( new TMethodEventJob<IpcServer>(
this, &IpcServer::handleClientConnecting)); this, &IpcServer::handleClientConnecting));
} }
IpcServer::~IpcServer() IpcServer::~IpcServer()
{ {
if (m_mock) {
return;
}
if (m_socket != nullptr) {
delete m_socket;
}
ARCH->lockMutex(m_clientsMutex); ARCH->lockMutex(m_clientsMutex);
ClientList::iterator it; ClientList::iterator it;
for (it = m_clients.begin(); it != m_clients.end(); it++) { for (it = m_clients.begin(); it != m_clients.end(); it++) {
@ -71,19 +84,19 @@ IpcServer::~IpcServer()
ARCH->unlockMutex(m_clientsMutex); ARCH->unlockMutex(m_clientsMutex);
ARCH->closeMutex(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 void
IpcServer::listen() IpcServer::listen()
{ {
m_socket.bind(m_address); m_socket->bind(m_address);
} }
void void
IpcServer::handleClientConnecting(const Event&, void*) IpcServer::handleClientConnecting(const Event&, void*)
{ {
synergy::IStream* stream = m_socket.accept(); synergy::IStream* stream = m_socket->accept();
if (stream == NULL) { if (stream == NULL) {
return; return;
} }

View File

@ -49,17 +49,17 @@ public:
//@{ //@{
//! Opens a TCP socket only allowing local connections. //! Opens a TCP socket only allowing local connections.
void listen(); virtual void listen();
//! Send a message to all clients matching the filter type. //! 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 //! @name accessors
//@{ //@{
//! Returns true when there are clients of the specified type connected. //! 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: private:
typedef std::list<IpcClientProxy*> ClientList; typedef std::list<IpcClientProxy*> ClientList;
TCPListenSocket m_socket; bool m_mock;
IEventQueue* m_events;
SocketMultiplexer* m_socketMultiplexer;
TCPListenSocket* m_socket;
NetworkAddress m_address; NetworkAddress m_address;
ClientList m_clients; ClientList m_clients;
ArchMutex m_clientsMutex; ArchMutex m_clientsMutex;
IEventQueue* m_events;
#ifdef TEST_ENV
public:
IpcServer() :
m_mock(true),
m_events(nullptr),
m_socketMultiplexer(nullptr),
m_socket(nullptr) { }
#endif
}; };

View File

@ -63,6 +63,7 @@ ToolApp::run(int argc, char** argv)
return kExitFailed; return kExitFailed;
} }
else { else {
// HACK: send to standard out so watchdog can parse.
std::cout << "activeDesktop:" << name.c_str() << std::endl; std::cout << "activeDesktop:" << name.c_str() << std::endl;
} }
#endif #endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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));
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<const IpcMessage&> IpcLogLineMessageEq(const String& s) {
const Matcher<const IpcLogLineMessage&> m(
Property(&IpcLogLineMessage::logLine, StrEq(s)));
return MatcherCast<const IpcMessage&>(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