shellsim 0.1.10__tar.gz → 0.1.11__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.
Files changed (184) hide show
  1. {shellsim-0.1.10 → shellsim-0.1.11}/Cargo.lock +1 -1
  2. {shellsim-0.1.10 → shellsim-0.1.11}/Cargo.toml +1 -1
  3. {shellsim-0.1.10 → shellsim-0.1.11}/PKG-INFO +1 -1
  4. {shellsim-0.1.10 → shellsim-0.1.11}/python/native/Cargo.lock +2 -2
  5. {shellsim-0.1.10 → shellsim-0.1.11}/python/native/Cargo.toml +1 -1
  6. {shellsim-0.1.10 → shellsim-0.1.11}/src/exec.rs +8 -16
  7. {shellsim-0.1.10 → shellsim-0.1.11}/src/harness.rs +5 -1
  8. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/native.rs +20 -1
  9. shellsim-0.1.11/src/python/stdlib/asyncio.rs +232 -0
  10. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/asyncio.py +206 -15
  11. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/host.rs +25 -2
  12. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/native_runtime.rs +71 -0
  13. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm.rs +6 -5
  14. {shellsim-0.1.10 → shellsim-0.1.11}/src/scheduler.rs +84 -31
  15. shellsim-0.1.10/src/python/stdlib/asyncio.rs +0 -47
  16. {shellsim-0.1.10 → shellsim-0.1.11}/LICENSE +0 -0
  17. {shellsim-0.1.10 → shellsim-0.1.11}/README.md +0 -0
  18. {shellsim-0.1.10 → shellsim-0.1.11}/pyproject.toml +0 -0
  19. {shellsim-0.1.10 → shellsim-0.1.11}/python/native/src/lib.rs +0 -0
  20. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/__init__.py +0 -0
  21. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/__main__.py +0 -0
  22. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/_api.py +0 -0
  23. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/_cli.py +0 -0
  24. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/py.typed +0 -0
  25. {shellsim-0.1.10 → shellsim-0.1.11}/python/shellsim/python.py +0 -0
  26. {shellsim-0.1.10 → shellsim-0.1.11}/rust-toolchain.toml +0 -0
  27. {shellsim-0.1.10 → shellsim-0.1.11}/src/bin/shellsim-python.rs +0 -0
  28. {shellsim-0.1.10 → shellsim-0.1.11}/src/clock.rs +0 -0
  29. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/archives.rs +0 -0
  30. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/awk/ast.rs +0 -0
  31. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/awk/eval.rs +0 -0
  32. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/awk/lexer.rs +0 -0
  33. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/awk/parser.rs +0 -0
  34. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/awk.rs +0 -0
  35. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/builtins.rs +0 -0
  36. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/dd.rs +0 -0
  37. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/echo.rs +0 -0
  38. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/find.rs +0 -0
  39. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/fs.rs +0 -0
  40. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/apply.rs +0 -0
  41. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/commit.rs +0 -0
  42. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/compare.rs +0 -0
  43. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/config.rs +0 -0
  44. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/conflict.rs +0 -0
  45. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/diff.rs +0 -0
  46. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/ignore.rs +0 -0
  47. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/import.rs +0 -0
  48. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/log.rs +0 -0
  49. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/merge.rs +0 -0
  50. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/mod.rs +0 -0
  51. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/plumbing.rs +0 -0
  52. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/rebase.rs +0 -0
  53. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/refs.rs +0 -0
  54. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/repo.rs +0 -0
  55. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/stash.rs +0 -0
  56. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/switch.rs +0 -0
  57. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/git/worktree.rs +0 -0
  58. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/hashing.rs +0 -0
  59. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/makecmd.rs +0 -0
  60. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/mod.rs +0 -0
  61. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/net.rs +0 -0
  62. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/options.rs +0 -0
  63. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/patch.rs +0 -0
  64. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/pkg.rs +0 -0
  65. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/printf.rs +0 -0
  66. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/proc.rs +0 -0
  67. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/regex_compat.rs +0 -0
  68. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/rgcmd.rs +0 -0
  69. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/sed.rs +0 -0
  70. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/sort.rs +0 -0
  71. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/system.rs +0 -0
  72. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/tarcmd.rs +0 -0
  73. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/text.rs +0 -0
  74. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/unavailable.rs +0 -0
  75. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/util.rs +0 -0
  76. {shellsim-0.1.10 → shellsim-0.1.11}/src/commands/zipcmd.rs +0 -0
  77. {shellsim-0.1.10 → shellsim-0.1.11}/src/descriptors.rs +0 -0
  78. {shellsim-0.1.10 → shellsim-0.1.11}/src/expand.rs +0 -0
  79. {shellsim-0.1.10 → shellsim-0.1.11}/src/harness_manager.rs +0 -0
  80. {shellsim-0.1.10 → shellsim-0.1.11}/src/hashes.rs +0 -0
  81. {shellsim-0.1.10 → shellsim-0.1.11}/src/host_ingest.rs +0 -0
  82. {shellsim-0.1.10 → shellsim-0.1.11}/src/interp.rs +0 -0
  83. {shellsim-0.1.10 → shellsim-0.1.11}/src/jqcmd.rs +0 -0
  84. {shellsim-0.1.10 → shellsim-0.1.11}/src/lib.rs +0 -0
  85. {shellsim-0.1.10 → shellsim-0.1.11}/src/main.rs +0 -0
  86. {shellsim-0.1.10 → shellsim-0.1.11}/src/mcp.rs +0 -0
  87. {shellsim-0.1.10 → shellsim-0.1.11}/src/net.rs +0 -0
  88. {shellsim-0.1.10 → shellsim-0.1.11}/src/netcmd.rs +0 -0
  89. {shellsim-0.1.10 → shellsim-0.1.11}/src/process.rs +0 -0
  90. {shellsim-0.1.10 → shellsim-0.1.11}/src/pseudo_fs.rs +0 -0
  91. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/ast.rs +0 -0
  92. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/bytecode.rs +0 -0
  93. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/compiler.rs +0 -0
  94. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/filesystem.rs +0 -0
  95. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/heap.rs +0 -0
  96. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/http.rs +0 -0
  97. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/lexer.rs +0 -0
  98. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/mapping.rs +0 -0
  99. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/mod.rs +0 -0
  100. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/number.rs +0 -0
  101. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/object_model.rs +0 -0
  102. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/parser.rs +0 -0
  103. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/process.rs +0 -0
  104. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/protocol.rs +0 -0
  105. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/slice.rs +0 -0
  106. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/source.rs +0 -0
  107. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/argparse.rs +0 -0
  108. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/base64.rs +0 -0
  109. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/bisect.rs +0 -0
  110. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/collections.rs +0 -0
  111. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/core.rs +0 -0
  112. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/dataclasses.rs +0 -0
  113. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/enum.rs +0 -0
  114. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/frozen.rs +0 -0
  115. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/functools.rs +0 -0
  116. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/hashlib.rs +0 -0
  117. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/heapq.rs +0 -0
  118. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/http.rs +0 -0
  119. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/importlib.rs +0 -0
  120. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/itertools.rs +0 -0
  121. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/json.rs +0 -0
  122. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/math.rs +0 -0
  123. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/mod.rs +0 -0
  124. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/numpy.rs +0 -0
  125. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/os.rs +0 -0
  126. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/pytest.rs +0 -0
  127. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/re.rs +0 -0
  128. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/abc.py +0 -0
  129. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/base64.py +0 -0
  130. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/cmath.py +0 -0
  131. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/codecs.py +0 -0
  132. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/collections.py +0 -0
  133. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/complex.py +0 -0
  134. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/copy.py +0 -0
  135. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/csv.py +0 -0
  136. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/datetime.py +0 -0
  137. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/glob.py +0 -0
  138. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/hashlib.py +0 -0
  139. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/http.py +0 -0
  140. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/http_client.py +0 -0
  141. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/importlib.py +0 -0
  142. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/io.py +0 -0
  143. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/json.py +0 -0
  144. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/logging.py +0 -0
  145. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/numpy_random.py +0 -0
  146. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/os.py +0 -0
  147. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/pathlib.py +0 -0
  148. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/pytest.py +0 -0
  149. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/random.py +0 -0
  150. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/shutil.py +0 -0
  151. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/statistics.py +0 -0
  152. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/struct.py +0 -0
  153. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/subprocess.py +0 -0
  154. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/tempfile.py +0 -0
  155. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/textwrap.py +0 -0
  156. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/urllib.py +0 -0
  157. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/urllib_error.py +0 -0
  158. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/urllib_request.py +0 -0
  159. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/uuid.py +0 -0
  160. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/zipfile.py +0 -0
  161. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/source/zlib.py +0 -0
  162. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/string.rs +0 -0
  163. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/struct.rs +0 -0
  164. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/subprocess.rs +0 -0
  165. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/sys.rs +0 -0
  166. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/time.rs +0 -0
  167. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/typing.rs +0 -0
  168. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/unittest.rs +0 -0
  169. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/vfs.rs +0 -0
  170. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/stdlib/zlib.rs +0 -0
  171. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/string.rs +0 -0
  172. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/token.rs +0 -0
  173. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/calls.rs +0 -0
  174. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/dispatch.rs +0 -0
  175. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/iteration.rs +0 -0
  176. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/namespace.rs +0 -0
  177. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/objects.rs +0 -0
  178. {shellsim-0.1.10 → shellsim-0.1.11}/src/python/vm/operations.rs +0 -0
  179. {shellsim-0.1.10 → shellsim-0.1.11}/src/resources.rs +0 -0
  180. {shellsim-0.1.10 → shellsim-0.1.11}/src/sandbox.rs +0 -0
  181. {shellsim-0.1.10 → shellsim-0.1.11}/src/scenario.rs +0 -0
  182. {shellsim-0.1.10 → shellsim-0.1.11}/src/shell.rs +0 -0
  183. {shellsim-0.1.10 → shellsim-0.1.11}/src/telemetry.rs +0 -0
  184. {shellsim-0.1.10 → shellsim-0.1.11}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.10"
333
+ version = "0.1.11"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.10"
3
+ version = "0.1.11"
4
4
  edition = "2021"
5
5
  description = "In-process BusyBox-like environment for RL rollouts and agent tests"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shellsim
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: MacOS :: MacOS X
@@ -406,7 +406,7 @@ dependencies = [
406
406
 
407
407
  [[package]]
408
408
  name = "shellsim"
409
- version = "0.1.10"
409
+ version = "0.1.11"
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.10"
430
+ version = "0.1.11"
431
431
  dependencies = [
432
432
  "pyo3",
433
433
  "serde",
@@ -2,7 +2,7 @@
2
2
 
3
3
  [package]
4
4
  name = "shellsim-python-bindings"
5
- version = "0.1.10"
5
+ version = "0.1.11"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -405,7 +405,7 @@ enum ExpansionLocation {
405
405
  Redirect(usize),
406
406
  }
407
407
 
408
- #[derive(Clone, Copy, Debug, PartialEq, Eq)]
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 = matches!(
2511
- interp.scheduler.state(pid),
2512
- Some(crate::scheduler::TaskState::Blocked(
2513
- crate::scheduler::WaitReason::Timer(value)
2514
- | crate::scheduler::WaitReason::ChildDeadline(_, value)
2515
- | crate::scheduler::WaitReason::ChildActivityDeadline(_, value)
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, Copy, Debug, PartialEq, Eq, Serialize)]
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, Copy, Debug, PartialEq, Eq)]
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
+ }