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

Fix actor not passing additional callback arguments #3196

Conversation

emanueleperuffo
Copy link

@emanueleperuffo emanueleperuffo commented Oct 10, 2023

Before this change, Actor was not forwarding additional callback arguments and assumed the callback is always in the form (err, data) => {}.

There are examples where the callback has more than 2 arguments. For example, in the loadVectorTile function implementation, the callback that is passed directly to the actor is defined as (err?: Error | null, data?: ArrayBuffer | null, cacheControl?: string | null, expires?: string | null) => {}. In this case, Actor only passes data, forgetting cacheControl and expires.

Linked issue: fixes #3180

Launch Checklist

  • Confirm your changes do not include backports from Mapbox projects (unless with compliant license) - if you are not sure about this, please ask!
  • Briefly describe the changes in this PR.
  • Link to related issues.
  • Write tests for all new functionality.
  • Add an entry to CHANGELOG.md under the ## main section.

@codecov-commenter
Copy link

codecov-commenter commented Oct 10, 2023

Codecov Report

All modified lines are covered by tests ✅

Comparison is base (9f7e2a9) 75.11% compared to head (c78b5c4) 75.22%.
Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3196      +/-   ##
==========================================
+ Coverage   75.11%   75.22%   +0.10%     
==========================================
  Files         240      241       +1     
  Lines       19170    19244      +74     
  Branches     4325     4338      +13     
==========================================
+ Hits        14400    14476      +76     
+ Misses       4770     4768       -2     
Files Coverage Δ
src/util/actor.ts 80.43% <100.00%> (ø)

... and 8 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@HarelM
Copy link
Collaborator

HarelM commented Oct 10, 2023

Can you please write a test that this issue solves? While the unit test is great, I would like to see the actual bug this PR solves...

@HarelM
Copy link
Collaborator

HarelM commented Oct 10, 2023

P.S. thanks for taking the time to open this PR!

@emanueleperuffo
Copy link
Author

Hi @HarelM, can you help me here? How could I write such test?
I have added a stackblitz that I shared in the linked issue #3180, is it enough?

@HarelM
Copy link
Collaborator

HarelM commented Oct 11, 2023

Since this requires the usage of actors and in between thread commination, the only thing I can think of is a e2e test.
We have those under browser.test.ts - these test loads the browser and you can write some code to be executed.
You can mock the backend to return the relevant tiles I believe and change the expiry date to be minimal - a second or even less.
I can't think of a better way to test this...

@emanueleperuffo
Copy link
Author

Ok. I will write some tests as soon as I get a change...
I hope soon enough, as I'm going on holiday next week.

@emanueleperuffo
Copy link
Author

I have added the e2e test.

@HarelM
Copy link
Collaborator

HarelM commented Oct 12, 2023

Thanks!! Can you please verify that the test fails without the fix?

@emanueleperuffo
Copy link
Author

I verified it and it was failing. I guess though that trust is not ehough?

@HarelM
Copy link
Collaborator

HarelM commented Oct 12, 2023

Trust is enough 😀 you can create a PR with the test only to show that it is failing, but it's not necessary.

Thanks for all the hard work!

});

for (const [, count] of Object.entries(callCountByUrl)) {
expect(count).toBeGreaterThan(1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Greater than 3-4? If the timeout is for 5 sec and the cache is for 1 sec?

Copy link
Author

Choose a reason for hiding this comment

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

The test could become flaky, it's making real network requests. Maybe we can try with 3?

@@ -175,13 +175,13 @@ export class Actor {
if (task.error) {
callback(deserialize(task.error));
} else {
callback(null, deserialize(task.data));
callback(null, ...(deserialize(task.data) as any[]));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still don't fully understand how this solves the problem... I mean task.data can be anything, so why cache control and expiry are not part of it?
Also if we used to send the data as a single object, now it is spread, isn't it?

Copy link
Author

Choose a reason for hiding this comment

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

task.data is the 1st argument after the err argument of the callback.

As you can see from the piece of code in Actor.ts in the below screenshot, the done callback is what is passed to the real function, and probably in some other places that I ignore right now.

Actor in fact proxies to-from the web worker and keeps a cache of the real callbacks to call on the other side of the message (postMessage and message listener). It calls the function that has the logic with its new callback (here the done) instead of the real callback. Then it passes the result back to the real callback.

Prior to this change, ...data was just data. Any other argument after data was just dropped. So if in my function I called callback(null, data, cacheControl, expires), it was exactly like calling done(null, data) (here data is the array buffer of the vector tile in the example I gave with addProtocol). This because the callback is this done that you can see in Actor.

As you can see also the spread is both in the actor while it calls the real callback, but also in the done callback where it acquires the arguments, so it's in both places. Probably to make things more clear we could rename task.data to task.args.

Screenshot from 2023-10-12 14-41-22

Copy link
Collaborator

Choose a reason for hiding this comment

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

args makes more sense yes.
What do you mean by other places?

Copy link
Author

Choose a reason for hiding this comment

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

I have seen some other mention of cacheControl and expiry in the arguments of the callback passed to the function loading the geojson if I recall correctly.

Copy link
Author

Choose a reason for hiding this comment

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

For geojson sources I mean.

Copy link
Author

Choose a reason for hiding this comment

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

I have tried to take a look at renaming data to args, but I wouldn't feel safe in doing so because data and task are typed as any in a lot of places, and a refactor is not that easy... Maybe this is a task for the future when typing is a bit more strict in this repo.

Copy link
Collaborator

Choose a reason for hiding this comment

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

After reading the code a bit more and this change it seems too risky for me, as I don't see the full picture yet, and I'm not certain that this change doesn't break anything...
I would recommend first creating a PR which only add types so that we know we don't break anything (adding types doesn't change the code), and after that we can introduce this fix.
See the following comment for more information, there's also some implementation with types as far as I've seen in the contour repo: #3185 (comment)
cc: @msbarry

Sorry for dragging this, but better safe than sorry...

CHANGELOG.md Outdated Show resolved Hide resolved
@HarelM
Copy link
Collaborator

HarelM commented Oct 13, 2023

No need to close this, it can wait for the other PR I believe...

@emanueleperuffo
Copy link
Author

I have added a new commit improving the typings in Actor.

@HarelM
Copy link
Collaborator

HarelM commented Oct 25, 2023

Due to this issue and PR, I have started to replace all the actor communication layer with types and promises.
You can take a look here:
#3269
Latest code in this branch is the most advanced one.
I suggest opening a PR against these branches to fix what you have seen in this issue.
The types, messages and everything there is well defined and you should be able to add the missing data in the right place.
I would recommend closing this PR.

@emanueleperuffo emanueleperuffo force-pushed the fix-additional-callback-arguments branch from 24fac88 to 6d7c9b6 Compare October 25, 2023 13:33
@emanueleperuffo
Copy link
Author

emanueleperuffo commented Oct 25, 2023

Ok I understand.

However, would you mind giving a look at the last commit in my PR?

I have implemented some more strict typing for the messages: one for the request, one for the response and one for the cancel, instead of having a generic Message that doesn't distinguish and contains all the fields.

@HarelM
Copy link
Collaborator

HarelM commented Oct 25, 2023

I took it a step forward and added promises etc.
Take a look at what I did, you can even try my branch.
If there's a missing data that is not send somewhere you should be able to add it easily, most of the worker code is not typed well, I hope...

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

Successfully merging this pull request may close these issues.

addProtocol callback is not using cacheControl and expires
3 participants