Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/AdblockPlus/Platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ namespace AdblockPlus
std::shared_future<FilterEnginePtr> filterEngine;
std::shared_ptr<Updater> updater;
std::set<std::string> evaluatedJsSources;
std::mutex evaluatedJsSourcesMutex;
std::function<void(const std::string&)> GetEvaluateCallback();
};

Expand Down
3 changes: 2 additions & 1 deletion libadblockplus.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@
'test/Prefs.cpp',
'test/ReferrerMapping.cpp',
'test/UpdateCheck.cpp',
'test/WebRequest.cpp'
'test/WebRequest.cpp',
'test/UpdaterAndFilterEngineCreation.cpp'
],
'msvs_settings': {
'VCLinkerTool': {
Expand Down
11 changes: 8 additions & 3 deletions src/Platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ std::function<void(const std::string&)> Platform::GetEvaluateCallback()
// GetEvaluateCallback() method assumes that jsEngine is already created
return [this](const std::string& filename)
{
std::lock_guard<std::mutex> lock(evaluatedJsSourcesMutex);
if (evaluatedJsSources.find(filename) != evaluatedJsSources.end())
return; //NO-OP, file was already evaluated

Expand Down Expand Up @@ -119,11 +120,15 @@ FilterEngine& Platform::GetFilterEngine()

Updater& Platform::GetUpdater()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to have creation of Updater and evaluatedJsSources to be thread safe, but perhaps it's actually fine for present because I suspect that all these creations will be re-engineered soon anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, it crossed my mind when I started writing this code. For the Updater thread safety, since Updater does not require async callback from JS code for creation, I propose to change constructor to:

Updater& Platform::GetUpdater()
{
  {
    std::lock_guard<std::mutex> lock(modulesMutex);
    if (updater)
        return *updater;
  }
  GetJsEngine(); // ensures that JsEngine is instantiated
  {
    std::lock_guard<std::mutex> lock(modulesMutex);
    if (!updater)
        updater = std::make_shared<Updater>(jsEngine, GetEvaluateCallback());
    return *updater;
  }
}

And I'll add a dedicated mutex to serialize access to evaluatedJsSources.

if (updater == nullptr)
{
GetJsEngine(); // ensures that JsEngine is instantiated
updater = std::make_shared<Updater>(jsEngine, GetEvaluateCallback());
std::lock_guard<std::mutex> lock(modulesMutex);
if (updater)
return *updater;
}
GetJsEngine(); // ensures that JsEngine is instantiated
std::lock_guard<std::mutex> lock(modulesMutex);
if (!updater)
updater = std::make_shared<Updater>(jsEngine, GetEvaluateCallback());
return *updater;
}

Expand Down
172 changes: 172 additions & 0 deletions test/UpdaterAndFilterEngineCreation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-present eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus 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 Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/


#include <thread>
#include <vector>
#include <chrono>
#include <string>
#include <memory>

#include "BaseJsTest.h"

using namespace AdblockPlus;

namespace
{
class UpdaterAndFilterEngineCreationTest : public BaseJsTest
{
protected:

static const size_t COUNT = 100;
const std::string PROP_NAME = "patternsbackupinterval";
std::vector<std::thread> threadsVector;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, one can use AdblockPlus::AsyncExecutor from include/AdblockPlus/AsyncExecutor.h. It also should not be a member, just allocate it before the loop (below) and call asyncExecutor.Dispatch(the-same-labda).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll check it and use AsyncExecutor.

Updater* updaterAddrArray[COUNT];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about using of std::array here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good tip.

FilterEngine* filterAddrArray[COUNT];
DelayedWebRequest::SharedTasks webRequestTasks;
DelayedTimer::SharedTasks timerTasks;

void SetUp()
{
LazyFileSystem* fileSystem;
ThrowingPlatformCreationParameters platformParams;
platformParams.logSystem.reset(new LazyLogSystem());
platformParams.timer = DelayedTimer::New(timerTasks);
platformParams.fileSystem.reset(fileSystem = new LazyFileSystem());
platformParams.webRequest = DelayedWebRequest::New(webRequestTasks);
platform.reset(new Platform(std::move(platformParams)));
threadsVector.reserve(COUNT);
std::uninitialized_fill(updaterAddrArray, updaterAddrArray + COUNT, nullptr);
std::uninitialized_fill(filterAddrArray, filterAddrArray + COUNT, nullptr);
}

void CallGetUpdaterSimultaneously()
{
CallGetSimultaneously(true, false);
}

void CallGetFilterEngineSimultaneously()
{
CallGetSimultaneously(false, true);
}

void CallGetUpdaterAndGetFilterEngineSimultaneously()
{
CallGetSimultaneously(true, true);
}

private:

void CallGetSimultaneously(bool isUpdater, bool isFilterEngine)
{
for (size_t idx = 0; idx < COUNT; ++idx)
threadsVector.push_back(
std::thread([this, idx, isUpdater, isFilterEngine]() -> void {
Updater* updaterAddr = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these variables? It seems one can just assign an item in the array.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used them just to make the assignment in one place at the end of this method. But I can remove them.

FilterEngine* filterEngineAddr = nullptr;
if (isUpdater && isFilterEngine)
{
// some randomization in order of calling gets
if (idx % 2)
{
updaterAddr = &(platform->GetUpdater());
filterEngineAddr = &(platform->GetFilterEngine());
}
else
{
filterEngineAddr = &(platform->GetFilterEngine());
updaterAddr = &(platform->GetUpdater());
}
}
else if (isUpdater)
updaterAddr = &(platform->GetUpdater());
else if (isFilterEngine)
filterEngineAddr = &(platform->GetFilterEngine());

if (updaterAddr != nullptr)
updaterAddrArray[idx] = updaterAddr;

if (filterEngineAddr != nullptr)
filterAddrArray[idx] = filterEngineAddr;
}));

// join the threads
for (auto& th: threadsVector)
if (th.joinable())
th.join();
}
};
}

TEST_F(UpdaterAndFilterEngineCreationTest, TestFilterEngineSingleInstance)
{
CallGetFilterEngineSimultaneously();
FilterEngine* filterEngineAddr = filterAddrArray[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking it would be good to test just in case that the value of the first element is not nullptr since we expect it anyway.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

for (size_t i = 1; i < COUNT; ++i)
ASSERT_EQ(filterEngineAddr, filterAddrArray[i]);
}

TEST_F(UpdaterAndFilterEngineCreationTest, TestUpdaterSingleInstance)
{
CallGetUpdaterSimultaneously();
Updater* updaterAddr = updaterAddrArray[0];
for (size_t i = 1; i < COUNT; ++i)
ASSERT_EQ(updaterAddr, updaterAddrArray[i]);
}

TEST_F(UpdaterAndFilterEngineCreationTest, TestUpdaterAndFilterEngineCreationsDontCollide)
{
CallGetUpdaterAndGetFilterEngineSimultaneously();
ASSERT_EQ(filterAddrArray[0], filterAddrArray[COUNT - 1]);
ASSERT_EQ(updaterAddrArray[0], updaterAddrArray[COUNT - 1]);
}

TEST_F(UpdaterAndFilterEngineCreationTest, TestUpdaterAndFilterEngineCreationOrder1)
{
Updater& updater = platform->GetUpdater();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wonder, does it not work without that sleep?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, sleep is not needed.

FilterEngine& filterEngine = platform->GetFilterEngine();

int propFromUpdater = updater.GetPref(PROP_NAME).AsInt();
int propFromFilterEngine = filterEngine.GetPref(PROP_NAME).AsInt();
ASSERT_EQ(propFromUpdater, propFromFilterEngine);

int newPropValue = 8;
updater.SetPref(PROP_NAME, GetJsEngine().NewValue(newPropValue));
propFromUpdater = updater.GetPref(PROP_NAME).AsInt();
propFromFilterEngine = filterEngine.GetPref(PROP_NAME).AsInt();
ASSERT_EQ(propFromUpdater, newPropValue);
ASSERT_EQ(propFromFilterEngine, newPropValue);
}

TEST_F(UpdaterAndFilterEngineCreationTest, TestUpdaterAndFilterEngineCreationOrder2)
{
FilterEngine& filterEngine = platform->GetFilterEngine();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
Updater& updater = platform->GetUpdater();

int propFromFilterEngine = filterEngine.GetPref(PROP_NAME).AsInt();
int propFromUpdater = updater.GetPref(PROP_NAME).AsInt();
ASSERT_EQ(propFromUpdater, propFromFilterEngine);

int newPropValue = 18;
filterEngine.SetPref(PROP_NAME, GetJsEngine().NewValue(newPropValue));
propFromFilterEngine = filterEngine.GetPref(PROP_NAME).AsInt();
propFromUpdater = updater.GetPref(PROP_NAME).AsInt();
ASSERT_EQ(propFromUpdater, newPropValue);
ASSERT_EQ(propFromFilterEngine, newPropValue);
}