Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: AdblockPlusEngine/main.cpp

Issue 10580043: Run a single FilterEngine instance in a separate process (Closed)
Patch Set: Created May 16, 2013, 5:29 a.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: AdblockPlusEngine/main.cpp
===================================================================
new file mode 100644
--- /dev/null
+++ b/AdblockPlusEngine/main.cpp
@@ -0,0 +1,257 @@
+#include <AdblockPlus.h>
+#include <iostream>
+#include <ShlObj.h>
+#include <sstream>
+#include <vector>
+#include <Windows.h>
+#include <Sddl.h>
Wladimir Palant 2013/05/16 11:52:16 I think that the usual order of includes is from g
Felix Dahlke 2013/05/17 09:11:36 We're using alphabetical in most places, but it's
+
+namespace
+{
+ const std::wstring pipeName = L"\\\\.\\pipe\\adblockplusengine";
+ const int bufferSize = 1024;
+ std::auto_ptr<AdblockPlus::FilterEngine> filterEngine;
+ HANDLE filterEngineMutex;
+
+ class AutoHandle
+ {
+ public:
+ AutoHandle()
+ {
+ }
+
+ AutoHandle(HANDLE handle) : handle(handle)
+ {
+ }
+
+ ~AutoHandle()
+ {
+ CloseHandle(handle);
+ }
+
+ HANDLE get()
+ {
+ return handle;
+ }
+
+ private:
+ HANDLE handle;
+ static int references;
Wladimir Palant 2013/05/16 11:52:16 Unused member?
Felix Dahlke 2013/05/23 12:31:20 Done.
+
+ AutoHandle(const AutoHandle& autoHandle);
+ AutoHandle& operator=(const AutoHandle& autoHandle);
+ };
+
+ void Log(const std::string& message)
+ {
+ // TODO: Log to a log file
+ MessageBoxA(0, ("AdblockPlusEngine: " + message).c_str(), "", MB_OK);
+ }
+
+ void LogLastError(const std::string& message)
+ {
+ std::stringstream stream;
+ stream << message << " (Error code: " << GetLastError() << ")";
+ Log(stream.str());
+ }
+
+ void LogException(const std::exception& exception)
+ {
+ Log(std::string("An exception occurred: ") + exception.what());
+ }
+
+ std::string MarshalStrings(const std::vector<std::string>& strings)
+ {
+ // TODO: This is some pretty hacky marshalling, replace it with something more robust
Wladimir Palant 2013/05/16 11:52:16 That's an understatement :)
+ std::string marshalledStrings;
+ for (std::vector<std::string>::const_iterator it = strings.begin(); it != strings.end(); it++)
+ marshalledStrings += *it + ';';
+ return marshalledStrings;
+ }
+
+ std::vector<std::string> UnmarshalStrings(const std::string& message)
+ {
+ std::stringstream stream(message);
+ std::vector<std::string> strings;
+ std::string string;
+ while (std::getline(stream, string, ';'))
+ strings.push_back(string);
+ return strings;
+ }
+
+ std::string ToString(std::wstring value)
+ {
+ int size = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, 0, 0, 0, 0);
Wladimir Palant 2013/05/16 11:52:16 WideCharToMultiByte() will return 0 on error, this
Felix Dahlke 2013/05/23 12:31:20 Done. Learnt something here: I thought std::string
+ char* converted = new char[size];
Wladimir Palant 2013/05/16 11:52:16 std::auto_ptr please, don't delete manually.
Felix Dahlke 2013/05/23 12:31:20 Done.
+ WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, converted, size, 0, 0);
+ std::string string(converted);
Wladimir Palant 2013/05/16 11:52:16 string(converted, size) please - you have an expli
Felix Dahlke 2013/05/17 09:11:36 Writing directly to the string buffer is still not
Felix Dahlke 2013/05/23 12:31:20 Decided to keep the function after all. ToUTF8Stri
Wladimir Palant 2013/05/23 14:25:01 No real objections but the function name should at
Felix Dahlke 2013/05/23 19:10:44 Just copied the function from libadblockplus, alth
+ delete converted;
+ return string;
+ }
+
+ std::string ReadMessage(HANDLE pipe)
+ {
+ std::stringstream stream;
+ std::auto_ptr<char> buffer(new char[bufferSize]);
+ bool hasError;
+ do
+ {
+ DWORD bytesRead;
+ hasError = !ReadFile(pipe, buffer.get(), bufferSize * sizeof(char), &bytesRead, 0);
+ if (hasError && GetLastError() != ERROR_MORE_DATA)
+ {
+ std::stringstream stream;
+ stream << "Error reading from pipe: " << GetLastError();
+ throw std::runtime_error(stream.str());
+ }
+ stream << std::string(buffer.get(), bytesRead);
+ } while (hasError);
Wladimir Palant 2013/05/16 11:52:16 The flow is rather hard to follow here, particular
Felix Dahlke 2013/05/23 12:31:20 Done.
+ return stream.str();
+ }
+
+ void WriteMessage(HANDLE pipe, const std::string& message)
+ {
+ DWORD bytesWritten;
+ if (!WriteFile(pipe, message.c_str(), message.length(), &bytesWritten, 0))
+ throw std::runtime_error("Failed to write to pipe");
Wladimir Palant 2013/05/16 11:52:16 What if bytesWritten < message.length()? According
Felix Dahlke 2013/05/17 09:11:36 Yes, it's blocking. And writing long strings works
+ }
+
+ std::string HandleRequest(const std::vector<std::string>& strings)
+ {
+ std::string procedureName = strings[0];
+ if (procedureName == "Matches")
+ return filterEngine->Matches(strings[1], strings[2], strings[3]) ? "1" : "0";
+ if (procedureName == "GetElementHidingSelectors")
+ return MarshalStrings(filterEngine->GetElementHidingSelectors(strings[1]));
+ return "";
+ }
+
+ DWORD WINAPI ClientThread(LPVOID param)
+ {
+ HANDLE pipe = static_cast<HANDLE>(param);
+
+ try
+ {
+ std::string message = ReadMessage(pipe);
+ std::vector<std::string> strings = UnmarshalStrings(message);
+ WaitForSingleObject(filterEngineMutex, INFINITE);
Wladimir Palant 2013/05/16 11:52:16 Doesn't this duplicate the functionality of mutexe
Felix Dahlke 2013/05/23 12:31:20 Done.
+ std::string response = HandleRequest(strings);
+ ReleaseMutex(filterEngineMutex);
+ WriteMessage(pipe, response);
+ }
+ catch (const std::exception& e)
+ {
+ LogException(e);
+ }
+
+ FlushFileBuffers(pipe);
+ DisconnectNamedPipe(pipe);
+ CloseHandle(pipe);
Wladimir Palant 2013/05/16 11:52:16 Opening a new connection for each calls sounds lik
Felix Dahlke 2013/05/17 09:11:36 I previously read that there is a limit to the num
Felix Dahlke 2013/05/23 12:31:20 Began to work on this, but it's a bit tricky and I
+ return 0;
+ }
+}
+
+bool IsWindowsVistaOrLater()
Wladimir Palant 2013/05/16 11:52:16 Any reason why some functions are inside an empty
Felix Dahlke 2013/05/17 09:11:36 Forgot to move it there.
+{
+ OSVERSIONINFOEX osvi;
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ GetVersionEx(reinterpret_cast<LPOSVERSIONINFO>(&osvi));
+ return osvi.dwMajorVersion >= 6;
+}
+
+std::wstring GetAppDataPath()
+{
+ wchar_t appDataPath[MAX_PATH];
+ if (IsWindowsVistaOrLater())
+ {
+ WCHAR* dataPath;
+ if (FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, 0, &dataPath)))
Wladimir Palant 2013/05/16 11:52:16 I don't think that statically compiling against SH
Felix Dahlke 2013/05/23 12:31:20 I'll leave this to Oleksandr, since he's working o
Oleksandr 2013/05/23 12:38:11 We are not statically compiling against SHGetKnown
+ throw std::runtime_error("Unable to find app data directory");
+ wcscpy_s(appDataPath, dataPath);
Wladimir Palant 2013/05/16 11:52:16 Why do we need two copy operations here? Convertin
Felix Dahlke 2013/05/17 09:11:36 I think that's because SHGetKnownFolderPath wants
Wladimir Palant 2013/05/17 10:35:02 Then maybe get rid of that assumption? :) Declare
Felix Dahlke 2013/05/23 12:31:20 Done.
+ CoTaskMemFree(dataPath);
+ }
+ else
+ {
+ if (!SHGetSpecialFolderPath(0, appDataPath, CSIDL_LOCAL_APPDATA, true))
+ throw std::runtime_error("Unable to find app data directory");
+ }
+ return std::wstring(appDataPath) + L"\\AdblockPlus";
+}
+
+std::auto_ptr<AdblockPlus::FilterEngine> CreateFilterEngine()
+{
+ // TODO: Pass appInfo in, which should be sent by the client
+ AdblockPlus::JsEnginePtr jsEngine = AdblockPlus::JsEngine::New();
+ std::string dataPath = ToString(GetAppDataPath());
+ dynamic_cast<AdblockPlus::DefaultFileSystem*>(jsEngine->GetFileSystem().get())->SetBasePath(dataPath);
+ std::auto_ptr<AdblockPlus::FilterEngine> filterEngine(new AdblockPlus::FilterEngine(jsEngine));
+ int timeout = 5000;
+ while (!filterEngine->IsInitialized())
Wladimir Palant 2013/05/16 11:52:16 TODO comment here please - we don't really want to
Felix Dahlke 2013/05/17 09:11:36 I presume that's not necessary anymore, now that y
Wladimir Palant 2013/05/17 10:35:02 Not pushed yet but - yes, once that review is done
Felix Dahlke 2013/05/23 12:31:20 Done.
+ {
+ const int step = 10;
+ Sleep(step);
+ timeout -= step;
+ if (timeout <= 0)
+ throw std::runtime_error("Timeout while waiting for FilterEngine initialization");
+ }
+ std::vector<AdblockPlus::SubscriptionPtr> subscriptions = filterEngine->FetchAvailableSubscriptions();
+ // TODO: Select a subscription based on the language, not just the first one.
+ // This should ideally be done in libadblockplus.
+ AdblockPlus::SubscriptionPtr subscription = subscriptions[0];
+ subscription->AddToList();
+ return filterEngine;
+}
+
+HANDLE CreatePipe(const std::wstring& pipeName)
+{
+ SECURITY_ATTRIBUTES sa;
+ memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+
+ // Low mandatory label. See http://msdn.microsoft.com/en-us/library/bb625958.aspx
+ LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
+ PSECURITY_DESCRIPTOR securitydescriptor;
+ ConvertStringSecurityDescriptorToSecurityDescriptor(
+ LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &securitydescriptor, 0);
Wladimir Palant 2013/05/16 11:52:16 You are leaking this security descriptor, it needs
Felix Dahlke 2013/05/23 12:31:20 Done.
+
+ sa.lpSecurityDescriptor = securitydescriptor;
+ sa.bInheritHandle = TRUE;
+
+ return CreateNamedPipe(pipeName.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
+ PIPE_UNLIMITED_INSTANCES, bufferSize, bufferSize, 0, &sa);
+}
+
+int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
Wladimir Palant 2013/05/16 11:52:16 Second HINSTANCE parameter is hPrevInstance - you
Felix Dahlke 2013/05/23 12:31:20 Seems to be always 0 in Win32: http://blogs.msdn.c
Wladimir Palant 2013/05/23 14:25:01 Right, a bit of outdated knowledge on my end :-( F
Felix Dahlke 2013/05/23 19:10:44 How about we just try to create the pipe initially
Wladimir Palant 2013/05/23 20:10:15 Yes, that's something I thought about as well. Thi
+{
+ filterEngine = CreateFilterEngine();
+ filterEngineMutex = CreateMutex(0, false, 0);
+
+ for (;;)
+ {
+ HANDLE pipe = CreatePipe(pipeName);
+ if (pipe == INVALID_HANDLE_VALUE)
+ {
+ LogLastError("CreateNamedPipe failed");
+ return 1;
+ }
+
+ if (!ConnectNamedPipe(pipe, 0))
+ {
+ LogLastError("Client failed to connect");
+ CloseHandle(pipe);
+ continue;
+ }
+
+ // TODO: Count established connections, kill the engine when none are left
+
+ AutoHandle thread(CreateThread(0, 0, ClientThread, static_cast<LPVOID>(pipe), 0, 0));
+ if (!thread.get())
+ {
+ LogLastError("CreateThread failed");
+ return 1;
+ }
+ }
+
+ return 0;
+}
Wladimir Palant 2013/05/16 11:52:16 IMHO, ReadMessage(), WriteMessage(), MarshalString
Felix Dahlke 2013/05/17 09:11:36 Same here, there's a TODO for that :)

Powered by Google App Engine
This is Rietveld