shellsim 0.1.10__tar.gz → 0.1.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {shellsim-0.1.10 → shellsim-0.1.12}/Cargo.lock +1 -1
- {shellsim-0.1.10 → shellsim-0.1.12}/Cargo.toml +1 -1
- {shellsim-0.1.10 → shellsim-0.1.12}/PKG-INFO +1 -1
- {shellsim-0.1.10 → shellsim-0.1.12}/python/native/Cargo.lock +2 -2
- {shellsim-0.1.10 → shellsim-0.1.12}/python/native/Cargo.toml +1 -1
- {shellsim-0.1.10 → shellsim-0.1.12}/src/descriptors.rs +28 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/exec.rs +8 -16
- {shellsim-0.1.10 → shellsim-0.1.12}/src/harness.rs +5 -1
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/native.rs +20 -1
- shellsim-0.1.12/src/python/stdlib/asyncio.rs +232 -0
- shellsim-0.1.12/src/python/stdlib/source/asyncio.py +863 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/host.rs +25 -2
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/native_runtime.rs +107 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm.rs +6 -5
- {shellsim-0.1.10 → shellsim-0.1.12}/src/scheduler.rs +84 -31
- shellsim-0.1.10/src/python/stdlib/asyncio.rs +0 -47
- shellsim-0.1.10/src/python/stdlib/source/asyncio.py +0 -364
- {shellsim-0.1.10 → shellsim-0.1.12}/LICENSE +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/README.md +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/pyproject.toml +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/native/src/lib.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/__init__.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/__main__.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/_api.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/_cli.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/py.typed +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/python/shellsim/python.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/rust-toolchain.toml +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/bin/shellsim-python.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/clock.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/archives.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/awk/ast.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/awk/eval.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/awk/lexer.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/awk/parser.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/awk.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/builtins.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/dd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/echo.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/find.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/fs.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/apply.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/commit.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/compare.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/config.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/conflict.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/diff.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/ignore.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/import.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/log.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/merge.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/mod.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/plumbing.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/rebase.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/refs.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/repo.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/stash.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/switch.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/git/worktree.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/hashing.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/makecmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/mod.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/net.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/options.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/patch.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/pkg.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/printf.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/proc.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/regex_compat.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/rgcmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/sed.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/sort.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/system.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/tarcmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/text.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/unavailable.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/util.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/commands/zipcmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/expand.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/harness_manager.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/hashes.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/host_ingest.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/interp.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/jqcmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/lib.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/main.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/mcp.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/net.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/netcmd.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/process.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/pseudo_fs.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/ast.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/bytecode.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/compiler.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/filesystem.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/heap.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/http.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/lexer.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/mapping.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/mod.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/number.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/object_model.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/parser.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/process.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/protocol.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/slice.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/source.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/argparse.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/base64.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/bisect.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/collections.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/core.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/dataclasses.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/enum.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/frozen.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/functools.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/hashlib.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/heapq.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/http.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/importlib.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/itertools.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/json.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/math.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/mod.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/numpy.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/os.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/pytest.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/re.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/abc.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/base64.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/cmath.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/codecs.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/collections.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/complex.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/copy.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/csv.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/datetime.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/glob.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/hashlib.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/http.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/http_client.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/importlib.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/io.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/json.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/logging.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/numpy_random.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/os.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/pathlib.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/pytest.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/random.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/shutil.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/statistics.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/struct.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/subprocess.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/tempfile.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/textwrap.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/urllib.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/urllib_error.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/urllib_request.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/uuid.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/zipfile.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/source/zlib.py +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/string.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/struct.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/subprocess.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/sys.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/time.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/typing.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/unittest.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/vfs.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/stdlib/zlib.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/string.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/token.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/calls.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/dispatch.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/iteration.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/namespace.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/objects.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/python/vm/operations.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/resources.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/sandbox.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/scenario.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/shell.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/telemetry.rs +0 -0
- {shellsim-0.1.10 → shellsim-0.1.12}/src/vfs.rs +0 -0
|
@@ -406,7 +406,7 @@ dependencies = [
|
|
|
406
406
|
|
|
407
407
|
[[package]]
|
|
408
408
|
name = "shellsim"
|
|
409
|
-
version = "0.1.
|
|
409
|
+
version = "0.1.12"
|
|
410
410
|
dependencies = [
|
|
411
411
|
"base64",
|
|
412
412
|
"crc32fast",
|
|
@@ -427,7 +427,7 @@ dependencies = [
|
|
|
427
427
|
|
|
428
428
|
[[package]]
|
|
429
429
|
name = "shellsim-python-bindings"
|
|
430
|
-
version = "0.1.
|
|
430
|
+
version = "0.1.12"
|
|
431
431
|
dependencies = [
|
|
432
432
|
"pyo3",
|
|
433
433
|
"serde",
|
|
@@ -194,6 +194,34 @@ impl DescriptorArena {
|
|
|
194
194
|
})
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/// Whether retrying a blocked input read can now make progress.
|
|
198
|
+
pub(crate) fn input_readable(&self, id: DescriptionId) -> bool {
|
|
199
|
+
self.descriptions
|
|
200
|
+
.get(&id)
|
|
201
|
+
.is_some_and(|entry| match &entry.description {
|
|
202
|
+
OpenDescription::Input {
|
|
203
|
+
bytes,
|
|
204
|
+
cursor,
|
|
205
|
+
closed,
|
|
206
|
+
} => *cursor < bytes.len() || *closed,
|
|
207
|
+
_ => false,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Whether retrying a blocked pipe read can now return bytes or EOF.
|
|
212
|
+
pub(crate) fn pipe_readable(&self, id: PipeId) -> bool {
|
|
213
|
+
self.pipes
|
|
214
|
+
.get(&id)
|
|
215
|
+
.is_some_and(|pipe| !pipe.bytes.is_empty() || pipe.writers == 0)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Whether retrying a blocked pipe write can now write or report a closed reader.
|
|
219
|
+
pub(crate) fn pipe_writable(&self, id: PipeId) -> bool {
|
|
220
|
+
self.pipes
|
|
221
|
+
.get(&id)
|
|
222
|
+
.is_some_and(|pipe| pipe.readers == 0 || pipe.bytes.len() < pipe.capacity)
|
|
223
|
+
}
|
|
224
|
+
|
|
197
225
|
/// Append explicitly supplied bytes to a streaming input description.
|
|
198
226
|
pub fn append_input(&mut self, id: DescriptionId, input: &[u8]) -> Result<(), DescriptorError> {
|
|
199
227
|
let entry = self
|
|
@@ -405,7 +405,7 @@ enum ExpansionLocation {
|
|
|
405
405
|
Redirect(usize),
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
#[derive(Clone,
|
|
408
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
409
409
|
pub(crate) enum ShellPoll {
|
|
410
410
|
Pending,
|
|
411
411
|
Blocked(crate::scheduler::WaitReason),
|
|
@@ -2507,21 +2507,13 @@ fn handle_ready_events(interp: &mut Interp) -> Result<(), String> {
|
|
|
2507
2507
|
continue;
|
|
2508
2508
|
};
|
|
2509
2509
|
let deadline = event.id.deadline_ns();
|
|
2510
|
-
let timed_wait =
|
|
2511
|
-
|
|
2512
|
-
Some(crate::scheduler::TaskState::
|
|
2513
|
-
crate::scheduler::
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
| Some(crate::scheduler::TaskState::Stopped(
|
|
2518
|
-
crate::scheduler::StoppedTask::Blocked(
|
|
2519
|
-
crate::scheduler::WaitReason::Timer(value)
|
|
2520
|
-
| crate::scheduler::WaitReason::ChildDeadline(_, value)
|
|
2521
|
-
| crate::scheduler::WaitReason::ChildActivityDeadline(_, value)
|
|
2522
|
-
)
|
|
2523
|
-
)) if value == deadline
|
|
2524
|
-
);
|
|
2510
|
+
let timed_wait = match interp.scheduler.state(pid) {
|
|
2511
|
+
Some(crate::scheduler::TaskState::Blocked(reason))
|
|
2512
|
+
| Some(crate::scheduler::TaskState::Stopped(
|
|
2513
|
+
crate::scheduler::StoppedTask::Blocked(reason),
|
|
2514
|
+
)) => reason.accepts_timer(deadline),
|
|
2515
|
+
_ => false,
|
|
2516
|
+
};
|
|
2525
2517
|
if timed_wait {
|
|
2526
2518
|
interp
|
|
2527
2519
|
.scheduler
|
|
@@ -304,7 +304,7 @@ pub enum ProcessViewStatus {
|
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
/// Serializable scheduler wait reason without exposing implementation strings.
|
|
307
|
-
#[derive(Clone,
|
|
307
|
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
|
308
308
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
309
309
|
pub enum WaitReasonView {
|
|
310
310
|
Timer { deadline_ns: u64 },
|
|
@@ -315,6 +315,7 @@ pub enum WaitReasonView {
|
|
|
315
315
|
ChildActivity { pid: u32 },
|
|
316
316
|
ChildDeadline { pid: u32, deadline_ns: u64 },
|
|
317
317
|
ChildActivityDeadline { pid: u32, deadline_ns: u64 },
|
|
318
|
+
Any { reasons: Vec<WaitReasonView> },
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
/// One stable path-level difference from the last checkpoint.
|
|
@@ -1259,6 +1260,9 @@ fn wait_reason_view(reason: WaitReason) -> WaitReasonView {
|
|
|
1259
1260
|
WaitReason::ChildActivityDeadline(pid, deadline_ns) => {
|
|
1260
1261
|
WaitReasonView::ChildActivityDeadline { pid, deadline_ns }
|
|
1261
1262
|
}
|
|
1263
|
+
WaitReason::Any(reasons) => WaitReasonView::Any {
|
|
1264
|
+
reasons: reasons.into_iter().map(wait_reason_view).collect(),
|
|
1265
|
+
},
|
|
1262
1266
|
}
|
|
1263
1267
|
}
|
|
1264
1268
|
|
|
@@ -81,7 +81,7 @@ impl fmt::Debug for ValueKindDef {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/// Stable error categories produced by native Python operations.
|
|
84
|
-
#[derive(Clone,
|
|
84
|
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
85
85
|
pub(super) enum PyErrorKind {
|
|
86
86
|
Type,
|
|
87
87
|
Value,
|
|
@@ -298,6 +298,12 @@ pub(super) struct PyProcessOutput {
|
|
|
298
298
|
pub inherited_stderr: Vec<u8>,
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/// Result of probing a modeled process operation without driving the scheduler.
|
|
302
|
+
pub(super) enum PyProcessPoll<T> {
|
|
303
|
+
Ready(T),
|
|
304
|
+
Blocked(crate::scheduler::WaitReason),
|
|
305
|
+
}
|
|
306
|
+
|
|
301
307
|
/// Fully-owned request for a child that outlives the native launch call.
|
|
302
308
|
pub(super) struct PyProcessStartRequest {
|
|
303
309
|
pub argv: Vec<String>,
|
|
@@ -339,8 +345,19 @@ pub(super) trait PyProcessRunner {
|
|
|
339
345
|
fd: i32,
|
|
340
346
|
amount: Option<usize>,
|
|
341
347
|
) -> PyResult<Vec<u8>>;
|
|
348
|
+
fn try_read_pipe(
|
|
349
|
+
&mut self,
|
|
350
|
+
handle: PyProcessHandle,
|
|
351
|
+
fd: i32,
|
|
352
|
+
amount: Option<usize>,
|
|
353
|
+
) -> PyResult<PyProcessPoll<Vec<u8>>>;
|
|
342
354
|
/// Write bytes to a captured child stdin, cooperatively scheduling while the pipe is full.
|
|
343
355
|
fn write_pipe(&mut self, handle: PyProcessHandle, input: Vec<u8>) -> PyResult<usize>;
|
|
356
|
+
fn try_write_pipe(
|
|
357
|
+
&mut self,
|
|
358
|
+
handle: PyProcessHandle,
|
|
359
|
+
input: Vec<u8>,
|
|
360
|
+
) -> PyResult<PyProcessPoll<usize>>;
|
|
344
361
|
/// Close one parent-side captured stream endpoint.
|
|
345
362
|
fn close_pipe(&mut self, handle: PyProcessHandle, fd: i32) -> PyResult<()>;
|
|
346
363
|
fn send_signal(
|
|
@@ -557,6 +574,8 @@ pub(super) trait PyRuntime {
|
|
|
557
574
|
fn exception_type_name(&self, value: &PyValue) -> Option<&'static str>;
|
|
558
575
|
/// Return one interpreter-owned exception class from the runtime's closed type table.
|
|
559
576
|
fn exception_type(&self, name: &'static str) -> PyValue;
|
|
577
|
+
/// Suspend the enclosing logical process until any modeled resource becomes ready.
|
|
578
|
+
fn wait_on(&mut self, reasons: Vec<crate::scheduler::WaitReason>) -> PyResult<()>;
|
|
560
579
|
fn clock(&mut self) -> &mut dyn PyClock;
|
|
561
580
|
fn environment(&self) -> &dyn PyEnvironment;
|
|
562
581
|
fn filesystem(&mut self) -> &mut dyn PyFilesystem;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
//! VM primitives used by the deterministic `asyncio` compatibility module.
|
|
2
|
+
//!
|
|
3
|
+
//! Scheduling policy stays in frozen Python. This module exposes bounded coroutine stepping and
|
|
4
|
+
//! checked waits on virtual time and modeled process resources. It cannot acquire host threads,
|
|
5
|
+
//! clocks, processes, files, or network access.
|
|
6
|
+
|
|
7
|
+
use super::super::native::{
|
|
8
|
+
CallArgs, FunctionDef, ModuleDef, PyError, PyIterator, PyKind, PyList, PyProcessHandle,
|
|
9
|
+
PyProcessPoll, PyResult, PyRuntime, PyTuple, PyValueCast,
|
|
10
|
+
};
|
|
11
|
+
use super::super::Value;
|
|
12
|
+
|
|
13
|
+
pub(super) static MODULE: ModuleDef = ModuleDef {
|
|
14
|
+
name: "_asyncio",
|
|
15
|
+
functions: &[
|
|
16
|
+
FunctionDef {
|
|
17
|
+
module: "_asyncio",
|
|
18
|
+
name: "_step",
|
|
19
|
+
call: step,
|
|
20
|
+
},
|
|
21
|
+
FunctionDef {
|
|
22
|
+
module: "_asyncio",
|
|
23
|
+
name: "_is_coroutine",
|
|
24
|
+
call: is_coroutine,
|
|
25
|
+
},
|
|
26
|
+
FunctionDef {
|
|
27
|
+
module: "_asyncio",
|
|
28
|
+
name: "_monotonic_ns",
|
|
29
|
+
call: monotonic_ns,
|
|
30
|
+
},
|
|
31
|
+
FunctionDef {
|
|
32
|
+
module: "_asyncio",
|
|
33
|
+
name: "_wait_resources",
|
|
34
|
+
call: wait_resources,
|
|
35
|
+
},
|
|
36
|
+
FunctionDef {
|
|
37
|
+
module: "_asyncio",
|
|
38
|
+
name: "_process_poll",
|
|
39
|
+
call: process_poll,
|
|
40
|
+
},
|
|
41
|
+
FunctionDef {
|
|
42
|
+
module: "_asyncio",
|
|
43
|
+
name: "_process_read",
|
|
44
|
+
call: process_read,
|
|
45
|
+
},
|
|
46
|
+
FunctionDef {
|
|
47
|
+
module: "_asyncio",
|
|
48
|
+
name: "_process_write",
|
|
49
|
+
call: process_write,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
values: &[],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
fn step(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
56
|
+
args.expect_positional("_asyncio._step", 2, 2)?;
|
|
57
|
+
args.reject_keywords("_asyncio._step")?;
|
|
58
|
+
let coroutine = args.positional()[0];
|
|
59
|
+
if runtime.kind(&coroutine)? != PyKind::Generator {
|
|
60
|
+
return Err(PyError::type_error("expected a coroutine"));
|
|
61
|
+
}
|
|
62
|
+
let iterator = coroutine.cast::<PyIterator>(runtime)?;
|
|
63
|
+
let sent = args.positional()[1];
|
|
64
|
+
let (status, value) = runtime.coroutine_step(iterator, sent)?;
|
|
65
|
+
runtime.new_tuple(vec![Value::Int(i64::from(status)), value])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn is_coroutine(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
69
|
+
args.expect_positional("_asyncio._is_coroutine", 1, 1)?;
|
|
70
|
+
args.reject_keywords("_asyncio._is_coroutine")?;
|
|
71
|
+
Ok(Value::Bool(
|
|
72
|
+
runtime.kind(&args.positional()[0])? == PyKind::Generator,
|
|
73
|
+
))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn monotonic_ns(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
77
|
+
args.expect_positional("_asyncio._monotonic_ns", 0, 0)?;
|
|
78
|
+
args.reject_keywords("_asyncio._monotonic_ns")?;
|
|
79
|
+
runtime.clock().monotonic_ns().map(Value::Int)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn wait_resources(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
83
|
+
args.expect_positional("_asyncio._wait_resources", 1, 1)?;
|
|
84
|
+
args.reject_keywords("_asyncio._wait_resources")?;
|
|
85
|
+
let tokens = args.positional()[0].cast::<PyList>(runtime)?;
|
|
86
|
+
let tokens = runtime.list_items(tokens)?;
|
|
87
|
+
if tokens.len() > crate::scheduler::MAX_TASKS {
|
|
88
|
+
return Err(PyError::resource_error("too many asyncio resource waits"));
|
|
89
|
+
}
|
|
90
|
+
let mut reasons = Vec::with_capacity(tokens.len());
|
|
91
|
+
for token in tokens {
|
|
92
|
+
runtime.charge_cpu(1)?;
|
|
93
|
+
reasons.push(parse_wait_token(runtime, token)?);
|
|
94
|
+
}
|
|
95
|
+
runtime.wait_on(reasons)?;
|
|
96
|
+
Ok(Value::None)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fn process_poll(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
100
|
+
args.expect_positional("_asyncio._process_poll", 1, 1)?;
|
|
101
|
+
args.reject_keywords("_asyncio._process_poll")?;
|
|
102
|
+
let handle = process_handle(runtime, args.positional()[0])?;
|
|
103
|
+
Ok(runtime
|
|
104
|
+
.processes()
|
|
105
|
+
.poll(handle)?
|
|
106
|
+
.map_or(Value::None, |status| Value::Int(i64::from(status))))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn process_read(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
110
|
+
args.expect_positional("_asyncio._process_read", 3, 3)?;
|
|
111
|
+
args.reject_keywords("_asyncio._process_read")?;
|
|
112
|
+
let values = args.positional();
|
|
113
|
+
let handle = process_handle(runtime, values[0])?;
|
|
114
|
+
let fd = integer(runtime, values[1], "stream descriptor")?;
|
|
115
|
+
let fd = i32::try_from(fd).map_err(|_| PyError::value_error("invalid stream descriptor"))?;
|
|
116
|
+
let amount = integer(runtime, values[2], "read size")?;
|
|
117
|
+
let amount = if amount < 0 {
|
|
118
|
+
None
|
|
119
|
+
} else {
|
|
120
|
+
Some(
|
|
121
|
+
usize::try_from(amount)
|
|
122
|
+
.map_err(|_| PyError::overflow_error("read size is too large"))?,
|
|
123
|
+
)
|
|
124
|
+
};
|
|
125
|
+
match runtime.processes().try_read_pipe(handle, fd, amount)? {
|
|
126
|
+
PyProcessPoll::Ready(bytes) => {
|
|
127
|
+
let bytes = runtime.new_bytes(bytes)?;
|
|
128
|
+
poll_tuple(runtime, true, bytes, Value::None)
|
|
129
|
+
}
|
|
130
|
+
PyProcessPoll::Blocked(reason) => {
|
|
131
|
+
let token = wait_token(runtime, reason)?;
|
|
132
|
+
poll_tuple(runtime, false, Value::None, token)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn process_write(runtime: &mut dyn PyRuntime, args: CallArgs) -> PyResult {
|
|
138
|
+
args.expect_positional("_asyncio._process_write", 2, 2)?;
|
|
139
|
+
args.reject_keywords("_asyncio._process_write")?;
|
|
140
|
+
let values = args.positional();
|
|
141
|
+
let handle = process_handle(runtime, values[0])?;
|
|
142
|
+
let input = runtime
|
|
143
|
+
.bytes_value(&values[1])?
|
|
144
|
+
.ok_or_else(|| PyError::type_error("process input must be bytes"))?;
|
|
145
|
+
match runtime.processes().try_write_pipe(handle, input)? {
|
|
146
|
+
PyProcessPoll::Ready(written) => {
|
|
147
|
+
let written = i64::try_from(written)
|
|
148
|
+
.map_err(|_| PyError::overflow_error("write result is too large"))?;
|
|
149
|
+
poll_tuple(runtime, true, Value::Int(written), Value::None)
|
|
150
|
+
}
|
|
151
|
+
PyProcessPoll::Blocked(reason) => {
|
|
152
|
+
let token = wait_token(runtime, reason)?;
|
|
153
|
+
poll_tuple(runtime, false, Value::None, token)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn poll_tuple(runtime: &mut dyn PyRuntime, ready: bool, value: Value, token: Value) -> PyResult {
|
|
159
|
+
runtime.new_tuple(vec![Value::Bool(ready), value, token])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn process_handle(runtime: &dyn PyRuntime, value: Value) -> PyResult<PyProcessHandle> {
|
|
163
|
+
let pid = integer(runtime, value, "subprocess handle")?;
|
|
164
|
+
let pid = u32::try_from(pid).map_err(|_| PyError::value_error("invalid subprocess handle"))?;
|
|
165
|
+
Ok(PyProcessHandle { pid })
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fn integer(runtime: &dyn PyRuntime, value: Value, name: &str) -> PyResult<i64> {
|
|
169
|
+
runtime
|
|
170
|
+
.int_value(&value)
|
|
171
|
+
.ok_or_else(|| PyError::type_error(format!("{name} must be an integer")))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn parse_wait_token(
|
|
175
|
+
runtime: &mut dyn PyRuntime,
|
|
176
|
+
token: Value,
|
|
177
|
+
) -> PyResult<crate::scheduler::WaitReason> {
|
|
178
|
+
let token = token.cast::<PyTuple>(runtime)?;
|
|
179
|
+
let parts = runtime.tuple_items(token)?;
|
|
180
|
+
if parts.len() != 2 {
|
|
181
|
+
return Err(PyError::value_error("invalid asyncio resource token"));
|
|
182
|
+
}
|
|
183
|
+
let kind = runtime
|
|
184
|
+
.string_value(&parts[0])?
|
|
185
|
+
.ok_or_else(|| PyError::type_error("asyncio resource kind must be a string"))?;
|
|
186
|
+
let value = integer(runtime, parts[1], "asyncio resource identifier")?;
|
|
187
|
+
match kind.as_str() {
|
|
188
|
+
"timer" => u64::try_from(value)
|
|
189
|
+
.map(crate::scheduler::WaitReason::Timer)
|
|
190
|
+
.map_err(|_| PyError::value_error("invalid timer deadline")),
|
|
191
|
+
"input_readable" => u32::try_from(value)
|
|
192
|
+
.map(crate::scheduler::WaitReason::InputReadable)
|
|
193
|
+
.map_err(|_| PyError::value_error("invalid input descriptor")),
|
|
194
|
+
"pipe_readable" => u32::try_from(value)
|
|
195
|
+
.map(crate::scheduler::WaitReason::PipeReadable)
|
|
196
|
+
.map_err(|_| PyError::value_error("invalid pipe identifier")),
|
|
197
|
+
"pipe_writable" => u32::try_from(value)
|
|
198
|
+
.map(crate::scheduler::WaitReason::PipeWritable)
|
|
199
|
+
.map_err(|_| PyError::value_error("invalid pipe identifier")),
|
|
200
|
+
"child" => u32::try_from(value)
|
|
201
|
+
.map(crate::scheduler::WaitReason::Child)
|
|
202
|
+
.map_err(|_| PyError::value_error("invalid child identifier")),
|
|
203
|
+
"child_activity" => u32::try_from(value)
|
|
204
|
+
.map(crate::scheduler::WaitReason::ChildActivity)
|
|
205
|
+
.map_err(|_| PyError::value_error("invalid child identifier")),
|
|
206
|
+
_ => Err(PyError::value_error("unsupported asyncio resource kind")),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn wait_token(runtime: &mut dyn PyRuntime, reason: crate::scheduler::WaitReason) -> PyResult {
|
|
211
|
+
let (kind, value) = match reason {
|
|
212
|
+
crate::scheduler::WaitReason::Timer(value) => ("timer", u64_to_i64(value)?),
|
|
213
|
+
crate::scheduler::WaitReason::InputReadable(value) => ("input_readable", i64::from(value)),
|
|
214
|
+
crate::scheduler::WaitReason::PipeReadable(value) => ("pipe_readable", i64::from(value)),
|
|
215
|
+
crate::scheduler::WaitReason::PipeWritable(value) => ("pipe_writable", i64::from(value)),
|
|
216
|
+
crate::scheduler::WaitReason::Child(value) => ("child", i64::from(value)),
|
|
217
|
+
crate::scheduler::WaitReason::ChildActivity(value) => ("child_activity", i64::from(value)),
|
|
218
|
+
crate::scheduler::WaitReason::ChildDeadline(_, _)
|
|
219
|
+
| crate::scheduler::WaitReason::ChildActivityDeadline(_, _)
|
|
220
|
+
| crate::scheduler::WaitReason::Any(_) => {
|
|
221
|
+
return Err(PyError::runtime_error(
|
|
222
|
+
"unsupported nested asyncio resource wait",
|
|
223
|
+
))
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
let kind = runtime.new_string(kind.to_string())?;
|
|
227
|
+
runtime.new_tuple(vec![kind, Value::Int(value)])
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fn u64_to_i64(value: u64) -> PyResult<i64> {
|
|
231
|
+
i64::try_from(value).map_err(|_| PyError::overflow_error("timer deadline is too large"))
|
|
232
|
+
}
|