Skip to content

Commit e095bdd

Browse files
committed
Don't preload dynamic libraries in newer Python compat dates
This fixes some but not all of the test failures with the new package set and also should improve performance because it avoids loading unneeded dynamic libraries.
1 parent d756f4a commit e095bdd

File tree

18 files changed

+195
-150
lines changed

18 files changed

+195
-150
lines changed
Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# This file is automatically generated by the Pyodide build script repo
22
# (https://github.com/cloudflare/pyodide-build-scripts) and should not be manually modified.
33

4-
PACKAGES_20241218 = {
4+
PACKAGES_20250324_1 = {
55
"PyJWT": [
66
"jwt",
77
],
8+
"aiohappyeyeballs": [
9+
"aiohappyeyeballs",
10+
],
811
"aiohttp": [
912
"aiohttp",
1013
],
@@ -125,11 +128,50 @@ PACKAGES_20241218 = {
125128
"packaging": [
126129
"packaging",
127130
],
131+
"propcache": [
132+
"propcache",
133+
],
128134
"pycparser": [
129135
"pycparser",
130136
],
131137
"pydantic": [
132138
"pydantic",
139+
"pydantic.alias_generators",
140+
"pydantic.aliases",
141+
"pydantic.annotated_handlers",
142+
"pydantic.class_validators",
143+
"pydantic.color",
144+
"pydantic.config",
145+
"pydantic.dataclasses",
146+
"pydantic.datetime_parse",
147+
"pydantic.decorator",
148+
"pydantic.deprecated",
149+
"pydantic.env_settings",
150+
"pydantic.error_wrappers",
151+
"pydantic.errors",
152+
"pydantic.experimental",
153+
"pydantic.fields",
154+
"pydantic.functional_serializers",
155+
"pydantic.functional_validators",
156+
"pydantic.generics",
157+
"pydantic.json",
158+
"pydantic.json_schema",
159+
"pydantic.main",
160+
"pydantic.networks",
161+
"pydantic.parse",
162+
"pydantic.plugin",
163+
"pydantic.root_model",
164+
"pydantic.schema",
165+
"pydantic.tools",
166+
"pydantic.type_adapter",
167+
"pydantic.types",
168+
"pydantic.typing",
169+
"pydantic.utils",
170+
"pydantic.v1",
171+
"pydantic.validate_call_decorator",
172+
"pydantic.validators",
173+
"pydantic.version",
174+
"pydantic.warnings",
133175
],
134176
"pydantic_core": [
135177
"pydantic_core",

build/python_metadata.bzl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
load("//:build/python/packages_20240829_4.bzl", "PACKAGES_20240829_4")
2-
load("//:build/python/packages_20241218.bzl", "PACKAGES_20241218")
2+
load("//:build/python/packages_20250324_1.bzl", "PACKAGES_20250324_1")
33

44
# The below is a list of pyodide-lock.json files for each package bundle version that we support.
55
# Each of these gets embedded in the workerd and EW binary.
@@ -8,15 +8,15 @@ load("//:build/python/packages_20241218.bzl", "PACKAGES_20241218")
88
# the lock file.
99
PYTHON_LOCKFILES = {
1010
"20240829.4": "c2d9c67ea55a672b95a3beb8d66bfbe7df736edb4bb657383b263151e7e85ef4",
11-
"20241218": "1421e9351baf24ec44d82f78b9ac26e8e0e6595bfe3f626dedb33147bfcd1998",
11+
"20250324.1": "3e5a9317dc0cfcf63e556034bf0e87b958bd6debcfdccdfffc8ce477cc439626",
1212
}
1313

1414
# This is a dictionary mapping a Python version with its packages version.
1515
#
1616
# NOTE: this needs to be kept in sync with compatibility-date.capnp.
1717
PYTHON_VERSION_TO_PACKAGES = {
1818
"0.26.0a2": "20240829.4",
19-
"0.27.1": "20241218",
19+
"0.27.1": "20250324.1",
2020
"development": "20240829.4",
2121
}
2222

@@ -32,7 +32,7 @@ PYTHON_VERSION_TO_PACKAGES = {
3232
# first.
3333
PYTHON_IMPORTS_TO_TEST = {
3434
"20240829.4": PACKAGES_20240829_4,
35-
"20241218": PACKAGES_20241218,
35+
"20250324.1": PACKAGES_20250324_1,
3636
}
3737

3838
# Each new package bundle should contain the same packages as the previous. We verify this
@@ -42,8 +42,8 @@ def verify_no_packages_were_removed():
4242
for i in range(0, len(package_dates) - 1):
4343
curr_pkgs = PYTHON_IMPORTS_TO_TEST[package_dates[i]]
4444
next_pkgs = PYTHON_IMPORTS_TO_TEST[package_dates[i + 1]]
45-
for pkg in curr_pkgs:
46-
if pkg not in next_pkgs:
47-
fail(pkg + " from packages version ", package_dates[i], " not in ", package_dates[i + 1])
45+
missing_pkgs = [pkg for pkg in curr_pkgs if pkg not in next_pkgs]
46+
if missing_pkgs:
47+
fail(str(missing_pkgs) + " from packages version ", package_dates[i], " not in ", package_dates[i + 1])
4848

4949
verify_no_packages_were_removed()

src/pyodide/BUILD.bazel

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ const location = undefined;
113113
114114
function addEventListener(){}
115115
116-
function reportUndefinedSymbolsNoOp() {}
117-
118116
if (typeof FinalizationRegistry === "undefined") {
119117
globalThis.FinalizationRegistry = class FinalizationRegistry {
120118
register(){}
@@ -155,10 +153,6 @@ REPLACEMENTS = [
155153
"Date.now",
156154
"monotonicDateNow",
157155
],
158-
[
159-
"reportUndefinedSymbols()",
160-
"reportUndefinedSymbolsNoOp()",
161-
],
162156
[
163157
"crypto.getRandomValues(",
164158
"getRandomValues(Module, ",
@@ -177,9 +171,16 @@ REPLACEMENTS = [
177171
"eval(UTF8ToString(ptr))",
178172
"(() => {throw new Error('Internal Emscripten code tried to eval, this should not happen, please file a bug report with your requirements.txt file\\'s contents')})()",
179173
],
174+
# Dynamic linking patches:
175+
# library lookup
176+
[
177+
"!libData",
178+
"!(libData ??= patchDynlibLookup(Module, libName))",
179+
],
180+
# for ensuring memory base of dynlib is stable when restoring snapshots
180181
[
181-
"!libData&&flags.fs",
182-
"!(libData ??= patchDynlibLookup(Module, libName))&&flags.fs",
182+
"getMemory(",
183+
"Module.getMemoryPatched(Module, libName, ",
183184
],
184185
]
185186

src/pyodide/internal/loadPackage.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ import {
1818
USING_OLDEST_PACKAGES_VERSION,
1919
} from 'pyodide-internal:metadata';
2020
import {
21-
DYNLIB_PATH,
2221
VIRTUALIZED_DIR,
2322
STDLIB_PACKAGES,
24-
getSitePackagesPath,
2523
} from 'pyodide-internal:setupPackages';
2624
import { parseTarInfo } from 'pyodide-internal:tar';
2725
import { default as DiskCache } from 'pyodide-internal:disk_cache';

src/pyodide/internal/metadatafs.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ export function createMetadataFS(Module: Module): object {
5656
if (parent.tree == undefined) {
5757
throw new Error('cannot lookup directory, tree is undefined');
5858
}
59-
return parent.tree.get(name)!;
59+
const res = parent.tree.get(name);
60+
if (res === undefined) {
61+
throw new Module.FS.ErrnoError(44);
62+
}
63+
return res;
6064
},
6165
read(stream, position, buffer) {
6266
return MetadataReader.read(stream.node.index!, position, buffer);

src/pyodide/internal/pool/emscriptenSetup.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ function getPrepareFileSystem(pythonStdlib: ArrayBuffer): PreRunHook {
6262
try {
6363
const pymajor = Module._py_version_major();
6464
const pyminor = Module._py_version_minor();
65-
Module.FS.mkdirTree(`/lib/python${pymajor}.${pyminor}/site-packages`);
65+
Module.FS.sitePackages = `/lib/python${pymajor}.${pyminor}/site-packages`;
66+
Module.FS.sessionSitePackages = '/session' + Module.FS.sitePackages;
67+
Module.FS.mkdirTree(Module.FS.sitePackages);
6668
Module.FS.writeFile(
6769
`/lib/python${pymajor}${pyminor}.zip`,
6870
new Uint8Array(pythonStdlib),

src/pyodide/internal/setupPackages.ts

Lines changed: 9 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {
66
LOAD_WHEELS_FROM_R2,
77
LOCKFILE,
88
LOAD_WHEELS_FROM_ARTIFACT_BUNDLER,
9-
USING_OLDEST_PACKAGES_VERSION,
109
} from 'pyodide-internal:metadata';
1110
import { simpleRunPython } from 'pyodide-internal:util';
1211
import { default as EmbeddedPackagesTarReader } from 'pyodide-internal:packages_tar_reader';
1312
import { default as MetadataReader } from 'pyodide-internal:runtime-generated/metadata';
1413

1514
const canonicalizeNameRegex = /[-_.]+/g;
15+
const DYNLIB_PATH = '/usr/lib';
1616

1717
/**
1818
* Canonicalize a package name. Port of Python's packaging.utils.canonicalize_name.
@@ -153,8 +153,13 @@ class VirtualizedDir {
153153
}
154154

155155
mount(Module: Module, tarFS: EmscriptenFS<TarFSInfo>) {
156-
const path = getSitePackagesPath(Module);
157-
Module.FS.mount(tarFS, { info: this.rootInfo }, path);
156+
Module.FS.mkdirTree(Module.FS.sessionSitePackages);
157+
Module.FS.mount(
158+
tarFS,
159+
{ info: this.rootInfo },
160+
Module.FS.sessionSitePackages
161+
);
162+
Module.FS.mkdirTree(DYNLIB_PATH);
158163
Module.FS.mount(tarFS, { info: this.dynlibTarFs }, DYNLIB_PATH);
159164
}
160165
}
@@ -215,14 +220,6 @@ function disabledLoadPackage(): never {
215220
);
216221
}
217222

218-
export function getSitePackagesPath(Module: Module): string {
219-
const pymajor = Module._py_version_major();
220-
const pyminor = Module._py_version_minor();
221-
return `/session/lib/python${pymajor}.${pyminor}/site-packages`;
222-
}
223-
224-
export const DYNLIB_PATH = '/usr/lib';
225-
226223
/**
227224
* This mounts a TarFS representing the site-packages directory (which contains the Python packages)
228225
* and another TarFS representing the dynlib directory (where dynlibs like libcrypto.so live).
@@ -233,9 +230,6 @@ export const DYNLIB_PATH = '/usr/lib';
233230
*/
234231
export function mountSitePackages(Module: Module, pkgs: VirtualizedDir): void {
235232
const tarFS = createTarFS(Module);
236-
const site_packages = getSitePackagesPath(Module);
237-
Module.FS.mkdirTree(site_packages);
238-
Module.FS.mkdirTree(DYNLIB_PATH);
239233
if (!LOAD_WHEELS_FROM_R2 && !LOAD_WHEELS_FROM_ARTIFACT_BUNDLER) {
240234
// if we are not loading additional wheels, then we're done
241235
// with site-packages and we can mount it here. Otherwise, we must mount it in
@@ -262,55 +256,13 @@ export function mountWorkerFiles(Module: Module) {
262256
* Has to run after the runtime is initialized.
263257
*/
264258
export function adjustSysPath(Module: Module): void {
265-
const site_packages = getSitePackagesPath(Module);
259+
const site_packages = Module.FS.sessionSitePackages;
266260
simpleRunPython(
267261
Module,
268262
`import sys; sys.path.append("/session/metadata"); sys.path.append("${site_packages}"); del sys`
269263
);
270264
}
271265

272-
function recursiveDependencies(
273-
lockfile: PackageLock,
274-
names: string[]
275-
): PackageDeclaration[] {
276-
const toLoad = new Map();
277-
for (const name of names) {
278-
addPackageToLoad(lockfile, name, toLoad);
279-
}
280-
return Array.from(toLoad.values());
281-
}
282-
283-
/**
284-
* Recursively add a package and its dependencies to toLoad.
285-
* A helper function for recursiveDependencies.
286-
* @param name The package to add
287-
* @param toLoad The set of names of packages to load
288-
* @private
289-
*/
290-
function addPackageToLoad(
291-
lockfile: PackageLock,
292-
name: string,
293-
toLoad: Map<string, PackageDeclaration>
294-
): void {
295-
const normalizedName = canonicalizePackageName(name);
296-
if (toLoad.has(normalizedName)) {
297-
return;
298-
}
299-
const pkgInfo = lockfile.packages[normalizedName];
300-
if (!pkgInfo) {
301-
throw new Error(
302-
`It appears that a package ("${name}") you requested is not available yet in workerd. \n` +
303-
'If you would like this package to be included, please open an issue at https://github.com/cloudflare/workerd/discussions/new?category=python-packages.'
304-
);
305-
}
306-
307-
toLoad.set(normalizedName, pkgInfo);
308-
309-
for (let depName of pkgInfo.depends) {
310-
addPackageToLoad(lockfile, depName, toLoad);
311-
}
312-
}
313-
314266
export { REQUIREMENTS };
315267
export const TRANSITIVE_REQUIREMENTS =
316268
MetadataReader.getTransitiveRequirements();

0 commit comments

Comments
 (0)