-
Notifications
You must be signed in to change notification settings - Fork 79
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
Use elm-solve-deps-wasm instead of elm-json solve #558
Conversation
PS: it's expected that tests are failing since the starter |
To complement I've left untouched the Since I haven't figured out (yet) how to interact with async functions and wasm, I've made the wasm interfaces sync, and thus needed to have sync versions of file reading and http requests. This means I've added There are still a few console logs for now, that would eventually be removed or replaced by proper logging. |
I think I've figured out a way to use async functions with wasm and rust, but unfortunately, due to the contaminating nature of async, it requires changes all the way through elm-solve-deps to the pubgrub rust package. And there are other WIP in pubgrub that are more prioritized than async, so I wouldn't expect to be able to use |
It’s used to install Elm and elm-format for development of node-test-runner. I re-added elm-tooling to devDependencies (not dependencies) since it’s only used during development now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed some modifications directly to your branch, I hope that’s ok!
6adc8a4
to
bfc3e44
Compare
I haven't looked at the cache within solve.js
There are multiple caches inside this implementation. OnlineCacheVersion is
a hash map that corresponds to the Json answered by the package server at
the address /packages. For efficiency this is also stored on disk in all
home inside a pubgrub/ directory. Like so, when in online mode, we can
start by reading this file on dis, count package versions, and and only ask
the server the new packages at packages/since/count.
There is a temporary cache just for memoization of the answer to the
function listing existing versions for a given package. This is just to
avoid having to read the disk again to list installed versions and merge
with existing version according to the package website. (This merge of
local + server answer is to have compatibility with lamdera).
Finally, I also cache the JSON of each package when needing to read the
dependencies of a given package. Both in a memory cache and on disk inside
the pubgrub subdirectory inside the elm home.
…On Sun, Jan 2, 2022, 14:02 Simon Lydell ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In lib/DependencyProvider.js
<#558 (comment)>
:
> +// Cache of existing versions according to the package website.
+let onlineVersionsCache /*: Map<string, Array<string>> */ = new Map();
+
+// Memoization cache to avoid doing the same work twice in listAvailableVersions.
+// This is to be cleared before each call to solve_deps().
+const listVersionsMemoCache /*: Map<string, Array<string>> */ = new Map();
We already have a cache in Solve.js. Can you explain how this cache comes
into play?
—
Reply to this email directly, view it on GitHub
<#558 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAWFOCIUAMYOXOAT6OZLL33UUBEGVANCNFSM5K4IIYQQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
The way the cache in Solve.js works is this:
Reading that json file is very fast. The cache key is a SHA256 hash of the user’s elm.json, so it is only invalidated if the user changes their elm.json which usually isn’t that often. I know that Given the above, do you think the extra caching you built is needed? If so, can you give an example scenario? |
Ah yes it's a completely different level of caching. And I think all levels
are useful. Elm-json employs a similar caching strategy. It's just that the
nature of a dependency solver may need to ask some of the things I'm
caching multiple times.
…On Sun, Jan 2, 2022, 15:49 Simon Lydell ***@***.***> wrote:
The way the cache in Solve.js works is this:
1. Try to read
elm-stuff/generated-code/elm-community/elm-test/0.19.1-revision6/dependencies.SHA256.json
from disk.
2. If it exists, use that (it contains the "dependencies" field value
for the generated Elm application used to run the tests).
3. Otherwise compute that.
Reading that json file is very fast.
The cache key is a SHA256 hash of the user’s elm.json, so it is only
invalidated if the user changes their elm.json which usually isn’t that
often.
I know that elm-json stores things in ~/.elm, but I’m not sure how that
affects things the times elm-json is actually invoked.
Given the above, do you think the extra caching you built is needed? If
so, can you give an example scenario?
—
Reply to this email directly, view it on GitHub
<#558 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAWFOCMCYMFDQC54CWFO4FDUUBQWBANCNFSM5K4IIYQQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
So, what’s an example scenario where the extra caching leads to a faster experience? |
There are few scenarios I have in mind such as: Create another project with the same dependencies than the current projectLet's say you use some project as a starter for another project. Change a few things like the package name, or the source location. But the dependencies stay unchanged. All files needed to solve the dependencies will already be available offline, so no need to make a request to the package server. Add a dependencyWith the caches, most of what is needed is already on disk. Most of the time, only few new requests will be needed instead of 1 + 1 per dependency. Sometimes even 1 request will be enough. If the offline solver fails, there is one mandatory request done to // curl -L https://package.elm-lang.org/all-packages/since/12970 | jq
[
"bChiquet/[email protected]",
"newmana/[email protected]",
"newmana/[email protected]"
] Then only when needing to fetch the dependencies of a given package for the first time, inside |
I think I like the direction of this, using a library (even if it is wasm) is much neater than shelling out to a different program. It has the green light from me in principle. |
what happens if a package is deleted and then a different package is
immediately uploaded?
In normal conditions, we request at since/${count-1} such that we also have
the latest package we know of in the answer from the server. As such, we
can verify that no package was removed.
Let's say the last package we know is A.
If A is deleted, and another package B is added, we will find B where we
expected A in the response and deduce that something not normal happened.
So we reset known versions.
If another package than A is deleted, and a new package C is added, it's
the same. Since C is more recent than A, it's C that will be the package at
the frontier and we will get C where we expected A in the response. In this
situation, we also reset packages.
In general, if any number of packages we knew are deleted, either there
will be an empty response, or, if new packages were added, there will be
the wrong package at position ${count} compared to what we were expecting,
and in these situations, we reset known packages.
If packages that we don't know yet are deleted before we update known
packages, it has no impact on our logic. It will be as if they never
existed. Other packages depending on those will just fail to resolve due to
no version existing for that dependency.
…On Tue, Jan 4, 2022, 21:11 Harry Sarson ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In lib/DependencyProvider.js
<#558 (comment)>
:
> +// Update the cache with a request to the package server.
+function updateCacheWithRequestSince(
+ cachePath /*: string */,
+ remotePackagesUrl /*: string */
+) /*: void */ {
+ // Count existing versions.
+ let versionsCount = 0;
+ for (const versions of onlineVersionsCache.values()) {
+ versionsCount += versions.length;
+ }
+
+ // Complete cache with a remote call to the package server.
+ const remoteUrl = remotePackagesUrl + '/since/' + (versionsCount - 1); // -1 to check if no package was deleted.
+ const newVersions = JSON.parse(request('GET', remoteUrl).getBody('utf8'));
+ if (newVersions.length === 0) {
+ // Reload from scratch since it means at least one package was deleted from the registry.
what happens if a package is deleted and then a different package is
immediately uploaded?
—
Reply to this email directly, view it on GitHub
<#558 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAWFOCKFSJ7WOPCOQEMBQZTUUNH5RANCNFSM5K4IIYQQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
ab0bc27
to
de4200a
Compare
FYI, I have a large app I can test it on. (Feel free to ping or @ me directly if needed.) Also, re node 12, imo I think it'd be fine to just drop it now, so long as there's a clear error message explaining that you need node 14+ if you try to run it on an earlier version (or does npm even have a way to mark the package as requiring a certain node version?) Anyone committed to using node 12 can just lock to the current elm-test version. |
Yes, package.json has the |
I don't think there is much left to discuss since the discussion happened on the other PR by @harrysarson . Now I guess we only need a bit of testing from people with big code base. |
One argument to keep compatibility with node 12 is that debian bullseye, which is the current stable debian uses Node 12.22. I have had issues raised about node 10 compatibility for elm-test-rs for the previous stable debian, so it might become an issue for elm-test too if we remove node 12 too early. Locking an elm-test version is an option, but it's possible they need to update for a security patch. I don't know, might be simpler to release a security patch for an older release. I think all should work with a minimum version of 12.20 (for the sync http requests via worker) so if we can keep it the minimum version let's keep it that way. |
agree about node 12. We should update the minimum version in package.json (currently it is 12.13 I think) and possibly also ensure that our CI checks node 12.20 |
ebb823e
to
c1f27d5
Compare
I added |
@@ -13,7 +13,7 @@ jobs: | |||
strategy: | |||
matrix: | |||
os: [ubuntu-latest, macOS-latest, windows-latest] | |||
node-version: [12.x, 14.x, 16.x, 17.x] | |||
node-version: [12.20.0, 12.x, 14.x, 16.x, 17.x] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth testing both 12.20.0 and latest 12.x, or would it be enough with just 12.20.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Felt like both the explicit minimum version, and the likely version are worth checking but 🤷
Most likely it's not a problem to have that duplicate because if we change the minimum version in the code it will fail on CI and we will change it there too. |
Trying this out on my work project(s) with Everything seems to work when using npm 6.14.16! But when using npm 8.5.5 (node 14.19.1),
Seemingly when trying to run the |
@avh4 It seems that npm v6 does not run Then I suppose the error stems from the fact that elm-tooling is in the dev dependencies, so I suppose it is not installed and not accessible for a global install when |
ah yeah, that analysis sounds correct then. I'm not seeing any npm hooks that would be appropriate for this (post-install script that is only for when dev dependencies are installed). Maybe the solution is to change the prepare script to be |
That would not be windows-compatible would it? |
@avh4 You can try adding |
Oh, npm scripts still don't get I'll see if |
But it’s still a good point of |
So it seems we have the green light to merge, and the elm-tooling prepare stuff can be fixed later in another PR since it's already impacting the current version of node-test-runner. Anything else you want to check @harrysarson ? |
There are a couple of changes I might suggest (in a future PR) so let's give this 1 month or so on master before cutting the new release |
@mpizenberg do you want to draft a commit message for the squashed commit? |
Use a custom dependency solver library instead of the elm-json binary. This PR introduces a custom dependency solver, extracted from the solver in elm-test-rs, improving the compatibility with other elm-based platforms such as Lamdera. The core algorithmic part of the solver, called "pubgrub", is rooted in the same logic than used in elm-json but implemented differently in the rust package pub fn solve_deps(
project_elm_json_str: &str,
use_test: bool,
// additional_constraints_str: &HashMap<String, Constraint>,
additional_constraints_str: JsValue,
// js_fetch_elm_json(pkg: &str, version: &str) -> String;
js_fetch_elm_json: js_sys::Function,
// js_list_available_versions(pkg: &str) -> Vec<String>;
js_list_available_versions: js_sys::Function,
) -> Result<JsValue, JsValue> { In this PR, the JS overlay implementing those two // same interface for solveOnline
solveOffline(
elmJson /*: string */,
useTest /*: boolean */,
extra /*: { [string]: string } */
) /*: string */ { ... } Remark: since requests for the online solver need to perform http requests in a synchronous manner, an approach based on a web worker thread is used, requiring a minimum node version of 12.20.0. |
Above is an attempt at a summary of this PR for a commit message. Feel free to change/remove/add anything. |
Thanks! |
Hey, I discussed this a little bit over the incremental elm discord, and this is what using
elm-solve-deps-wasm
inside node-test-runner would look like.elm-solve-deps-wasm basically is a WebAssembly port of the dependency solver inside elm-test-rs. Advantages over using
elm-json
as a binary are the versatility of the solver that you can customize to your needs (can work with lamdera for example), and lightweight compared to downloading a binary. The wasm crate is 100kb compressed and 280kb total package uncompressed.On the downsides, it's currently not much tested, has worse error messages than elm-json, and customizability implies a bit more code (cf
dependency-provider-offline.js
provided as starting point).Let me know what you think of this.