Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring task #6

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

kzlomek
Copy link

@kzlomek kzlomek commented Jul 23, 2018

No description provided.

kzlomek added 4 commits July 19, 2018 08:55
Created Updater class for handling application updates and moved
respective API from FilterEngine to Updater component.
For now Updater class does not care about asynchronicity (if any)
in JS files which are used for update checks logic.
TODO: Move Updater class to dedicated .h and .cpp files.

Abstracted jsSources from FilterEngine. Now every component (like
FilterEngine and Updater) created by Platfrom gets EvaluateCallback
and evaluates own JS files needed for internal logic.

EvaluateCallback keeps track of evaluated files to avoid duplicated
evaluations.
Moved Updater class to dedicated header and source files.

Created apiUpdater.js with a separate API for Updater component.

Issues to fix:

1. Pass Prefs to Updater component
- Now Updater sets just an empty Prefs object

2. Review content of apiUpdater.js and updaterJsFiles[] array
- Check that all what's needed is set there and nothing is redundant
- Does Updater component need to take care of asynchronicity in JS
files?

3. Fix UpdateCheckTests
- They are now passing just because FilterEngine object is created
while those tests should just rely only on Updater object.
Added cpp methods for Prefs JS API.
Removed not needed JS API.
Added _isSubscriptionDownloadAllowed event callback to unblock
downloads in JS code.
Cleaned up UpdateCheckTest a bit removing FilterEngine because
with the change above those tests don't require anymore
FilterEnigne object to pass.
Copy link
Contributor

@abby-sergz abby-sergz left a comment

Choose a reason for hiding this comment

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

It looks pretty good, there are just some peculiarities.

* Callback type for evaluating JS expression.
* The parameter is the JS file name containing the expression.
*/
typedef std::function<void(const std::string&)> EvaluateCallback;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's actually unrelated to JsEngine, it's rather an interface of FilterEngine and Updater, therefore IMO it would be better to define it there, thus two separate typedefs in both FilterEngine and Updater.

#include "JsContext.h"
#include "Thread.h"
#include <mutex>
#include <condition_variable>

using namespace AdblockPlus;

extern std::string jsSources[];
namespace {
static std::string filterEngineJsFiles[] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not necessary to make it static but it should be const.

#include "JsContext.h"
#include "Thread.h"
#include <mutex>
#include <condition_variable>

using namespace AdblockPlus;

extern std::string jsSources[];
namespace {
Copy link
Contributor

Choose a reason for hiding this comment

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

The coding style in this project implies that { are located on a new line, could you please move it there?

// Set the preconfigured prefs
auto preconfiguredPrefsObject = jsEngine->NewObject();
for (const auto& pref : params.preconfiguredPrefs)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

This change seem unrelated, could you please revert it back?

@@ -228,7 +262,7 @@ void FilterEngine::CreateAsync(const JsEnginePtr& jsEngine,
isSubscriptionDownloadAllowedCallback(params[0].IsString() ? &allowedConnectionType : nullptr, callJsCallback);
});
}

Copy link
Contributor

Choose a reason for hiding this comment

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

This change seem unrelated, could you please revert it back?

src/Platform.cpp Outdated
return; //NO-OP, file was already evaluated

// Lock the JS engine while we are loading scripts
const JsContext context(*jsEngine);
Copy link
Contributor

Choose a reason for hiding this comment

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

The lock is not necessary here.

Copy link
Author

Choose a reason for hiding this comment

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

Please check my response to your comment about removing "const JsContext context(*jsEngine);" from FilterEngine::CreateAsync().

Copy link
Author

Choose a reason for hiding this comment

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

My previous comment is not valid anymore.

src/Updater.cpp Outdated
: jsEngine(jsEngine), updateCheckId(0)
{
// Hack to enable downloads from JS (see compat.js)
jsEngine->SetEventCallback("_isSubscriptionDownloadAllowed", [this](JsValueList&& params)
Copy link
Contributor

Choose a reason for hiding this comment

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

this code is unrelated to the Updater, could you please not bring it here?

Copy link
Author

Choose a reason for hiding this comment

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

Indeed, it is a bad hack. For the time being, to enable Updater downloads is JS code, that is to not rely on _isSubscriptionDownloadAllowed, I propose to add following execution path to send() method in compat.js just before _isSubscriptionDownloadAllowed() is executed:
if (this._url.includes("update.json"))
{
window._webRequest.GET(this._url, this._requestHeaders, onGetDone);
return;
}
Do you agree?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, it's required. I agree, let's add that check to compat.js.

src/Updater.cpp Outdated

// Set empty preconfigured prefs
auto preconfiguredPrefsObject = jsEngine->NewObject();
jsEngine->SetGlobalProperty("_preconfiguredPrefs", preconfiguredPrefsObject);
Copy link
Contributor

Choose a reason for hiding this comment

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

This code is actually unrelated, however, since prefs.js is required and prefs.js expects that object, I would propose to change prefs.js. The latter should rather check whether _preconfiguredPrefs exists. Additionally, it can happen that someone firstly instantiated Updater and then is instantiating FilterEngine, but prefs are already initialized. IMO we may not fully address it here but fix it later, under another ticket.

Copy link
Author

Choose a reason for hiding this comment

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

I'll remove this code from the Updater class and add a check to prefs.js to iterate through _preconfiguredPrefs only if this object exists. Is that enough for the scope of this task? Later on _preconfiguredPrefs could be split so FilterEngine and Updater would use dedicated objects.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's absolutely enough. I think later we should rather extract Prefs the similar way as Updater and make the former as a dependency for FilterEngine and Updater.


let API_UPDATER = (() =>
{
const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems Services is not used here, could you please remove it?

@@ -125,11 +133,15 @@ namespace AdblockPlus
TimerPtr timer;
FileSystemPtr fileSystem;
WebRequestPtr webRequest;
JsEngine::EvaluateCallback evaluateCallback;
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems unused, could you please remove it?

@kzlomek
Copy link
Author

kzlomek commented Aug 2, 2018

Thanks @abby-sergz, I've added my responses to clarify some things before I'll push new a commit which addresses review remarks.

Apart from changes in new code, this also withdraws
changes in existing code not related with this feature.
@kzlomek
Copy link
Author

kzlomek commented Aug 5, 2018

@abby-sergz , my commit from yesterday should address all review comments.

Copy link
Contributor

@abby-sergz abby-sergz left a comment

Choose a reason for hiding this comment

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

Could you please add a couple of tests instantiating both FilterEngine and Updater in different orders and I think it will be over.

@@ -91,6 +117,16 @@ FilterEngine& Platform::GetFilterEngine()
return *std::shared_future<FilterEnginePtr>(filterEngine).get();
}

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.

@kzlomek
Copy link
Author

kzlomek commented Aug 6, 2018

@abby-sergz , regarding requested tests do you want to check in those tests something specific to confirm that FilterEngine and Updater are operable, for example by calling Set(Get)Pref() on both of them?

@abby-sergz
Copy link
Contributor

abby-sergz commented Aug 6, 2018 via email

@kzlomek kzlomek force-pushed the refactoring_task branch 4 times, most recently from 7246e0a to 98318ef Compare August 8, 2018 06:14
Now creation of Updater is thread safe.
Added synchronization for evaluatedJsSources set.
Added more unit tests.
@kzlomek
Copy link
Author

kzlomek commented Aug 8, 2018

I've pushed commit "Fixed thread safety and added more tests" and then force pushed amendments in this commit with fixes in indentation and to resolve bug in the testes. I don't plan to update this commit so please review.


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.

static const size_t COUNT = 100;
const std::string PROP_NAME = "patternsbackupinterval";
std::vector<std::thread> threadsVector;
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.

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.

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.

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.

Copy link
Contributor

@abby-sergz abby-sergz left a comment

Choose a reason for hiding this comment

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

I think it's over, just a tiny thing is left there.

{
CallGetFilterEngineSimultaneously();
FilterEngine* filterEngineAddr = filterAddrArray[0];
ASSERT_NE(filterEngineAddr, nullptr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for not noticing it before. Even if it's a nullptr, we still can continue the execution of the test. It's a pretty subtle and perhaps even subjective thing, but could you please change all these ASSERT_* to EXPECT_*. It can seem inconsistent from other tests but we are gradually applying this approach.

Usually EXPECT_* are preferred, as they allow more than one failure to be reported in a test. However, you should use ASSERT_* if it doesn't make sense to continue when the assertion in question fails.

Therefore we usually use ASSERT_ in such cases when we know that the normal execution should not be possible, e.g. of a seg-fault because of dereferencing of a nullptr (in general case it can of course work without a seg-fault).

BTW, normally the order of arguments is that the expected value is going first and the actual value is the second one. Could you please change that order too if it does not cause compilation errors?

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 know this appoach - I'll change those nullptr checks from ASSERT to EXPECT and will update the order, I'll push new commit in couple of minutes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants