shellsim 0.1.11__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.
Files changed (183) hide show
  1. {shellsim-0.1.11 → shellsim-0.1.12}/Cargo.lock +1 -1
  2. {shellsim-0.1.11 → shellsim-0.1.12}/Cargo.toml +1 -1
  3. {shellsim-0.1.11 → shellsim-0.1.12}/PKG-INFO +1 -1
  4. {shellsim-0.1.11 → shellsim-0.1.12}/python/native/Cargo.lock +2 -2
  5. {shellsim-0.1.11 → shellsim-0.1.12}/python/native/Cargo.toml +1 -1
  6. {shellsim-0.1.11 → shellsim-0.1.12}/src/descriptors.rs +28 -0
  7. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/asyncio.py +324 -16
  8. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/native_runtime.rs +36 -0
  9. {shellsim-0.1.11 → shellsim-0.1.12}/LICENSE +0 -0
  10. {shellsim-0.1.11 → shellsim-0.1.12}/README.md +0 -0
  11. {shellsim-0.1.11 → shellsim-0.1.12}/pyproject.toml +0 -0
  12. {shellsim-0.1.11 → shellsim-0.1.12}/python/native/src/lib.rs +0 -0
  13. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/__init__.py +0 -0
  14. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/__main__.py +0 -0
  15. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/_api.py +0 -0
  16. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/_cli.py +0 -0
  17. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/py.typed +0 -0
  18. {shellsim-0.1.11 → shellsim-0.1.12}/python/shellsim/python.py +0 -0
  19. {shellsim-0.1.11 → shellsim-0.1.12}/rust-toolchain.toml +0 -0
  20. {shellsim-0.1.11 → shellsim-0.1.12}/src/bin/shellsim-python.rs +0 -0
  21. {shellsim-0.1.11 → shellsim-0.1.12}/src/clock.rs +0 -0
  22. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/archives.rs +0 -0
  23. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/awk/ast.rs +0 -0
  24. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/awk/eval.rs +0 -0
  25. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/awk/lexer.rs +0 -0
  26. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/awk/parser.rs +0 -0
  27. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/awk.rs +0 -0
  28. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/builtins.rs +0 -0
  29. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/dd.rs +0 -0
  30. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/echo.rs +0 -0
  31. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/find.rs +0 -0
  32. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/fs.rs +0 -0
  33. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/apply.rs +0 -0
  34. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/commit.rs +0 -0
  35. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/compare.rs +0 -0
  36. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/config.rs +0 -0
  37. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/conflict.rs +0 -0
  38. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/diff.rs +0 -0
  39. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/ignore.rs +0 -0
  40. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/import.rs +0 -0
  41. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/log.rs +0 -0
  42. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/merge.rs +0 -0
  43. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/mod.rs +0 -0
  44. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/plumbing.rs +0 -0
  45. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/rebase.rs +0 -0
  46. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/refs.rs +0 -0
  47. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/repo.rs +0 -0
  48. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/stash.rs +0 -0
  49. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/switch.rs +0 -0
  50. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/git/worktree.rs +0 -0
  51. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/hashing.rs +0 -0
  52. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/makecmd.rs +0 -0
  53. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/mod.rs +0 -0
  54. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/net.rs +0 -0
  55. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/options.rs +0 -0
  56. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/patch.rs +0 -0
  57. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/pkg.rs +0 -0
  58. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/printf.rs +0 -0
  59. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/proc.rs +0 -0
  60. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/regex_compat.rs +0 -0
  61. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/rgcmd.rs +0 -0
  62. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/sed.rs +0 -0
  63. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/sort.rs +0 -0
  64. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/system.rs +0 -0
  65. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/tarcmd.rs +0 -0
  66. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/text.rs +0 -0
  67. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/unavailable.rs +0 -0
  68. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/util.rs +0 -0
  69. {shellsim-0.1.11 → shellsim-0.1.12}/src/commands/zipcmd.rs +0 -0
  70. {shellsim-0.1.11 → shellsim-0.1.12}/src/exec.rs +0 -0
  71. {shellsim-0.1.11 → shellsim-0.1.12}/src/expand.rs +0 -0
  72. {shellsim-0.1.11 → shellsim-0.1.12}/src/harness.rs +0 -0
  73. {shellsim-0.1.11 → shellsim-0.1.12}/src/harness_manager.rs +0 -0
  74. {shellsim-0.1.11 → shellsim-0.1.12}/src/hashes.rs +0 -0
  75. {shellsim-0.1.11 → shellsim-0.1.12}/src/host_ingest.rs +0 -0
  76. {shellsim-0.1.11 → shellsim-0.1.12}/src/interp.rs +0 -0
  77. {shellsim-0.1.11 → shellsim-0.1.12}/src/jqcmd.rs +0 -0
  78. {shellsim-0.1.11 → shellsim-0.1.12}/src/lib.rs +0 -0
  79. {shellsim-0.1.11 → shellsim-0.1.12}/src/main.rs +0 -0
  80. {shellsim-0.1.11 → shellsim-0.1.12}/src/mcp.rs +0 -0
  81. {shellsim-0.1.11 → shellsim-0.1.12}/src/net.rs +0 -0
  82. {shellsim-0.1.11 → shellsim-0.1.12}/src/netcmd.rs +0 -0
  83. {shellsim-0.1.11 → shellsim-0.1.12}/src/process.rs +0 -0
  84. {shellsim-0.1.11 → shellsim-0.1.12}/src/pseudo_fs.rs +0 -0
  85. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/ast.rs +0 -0
  86. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/bytecode.rs +0 -0
  87. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/compiler.rs +0 -0
  88. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/filesystem.rs +0 -0
  89. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/heap.rs +0 -0
  90. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/http.rs +0 -0
  91. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/lexer.rs +0 -0
  92. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/mapping.rs +0 -0
  93. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/mod.rs +0 -0
  94. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/native.rs +0 -0
  95. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/number.rs +0 -0
  96. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/object_model.rs +0 -0
  97. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/parser.rs +0 -0
  98. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/process.rs +0 -0
  99. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/protocol.rs +0 -0
  100. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/slice.rs +0 -0
  101. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/source.rs +0 -0
  102. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/argparse.rs +0 -0
  103. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/asyncio.rs +0 -0
  104. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/base64.rs +0 -0
  105. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/bisect.rs +0 -0
  106. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/collections.rs +0 -0
  107. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/core.rs +0 -0
  108. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/dataclasses.rs +0 -0
  109. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/enum.rs +0 -0
  110. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/frozen.rs +0 -0
  111. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/functools.rs +0 -0
  112. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/hashlib.rs +0 -0
  113. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/heapq.rs +0 -0
  114. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/http.rs +0 -0
  115. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/importlib.rs +0 -0
  116. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/itertools.rs +0 -0
  117. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/json.rs +0 -0
  118. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/math.rs +0 -0
  119. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/mod.rs +0 -0
  120. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/numpy.rs +0 -0
  121. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/os.rs +0 -0
  122. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/pytest.rs +0 -0
  123. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/re.rs +0 -0
  124. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/abc.py +0 -0
  125. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/base64.py +0 -0
  126. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/cmath.py +0 -0
  127. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/codecs.py +0 -0
  128. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/collections.py +0 -0
  129. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/complex.py +0 -0
  130. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/copy.py +0 -0
  131. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/csv.py +0 -0
  132. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/datetime.py +0 -0
  133. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/glob.py +0 -0
  134. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/hashlib.py +0 -0
  135. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/http.py +0 -0
  136. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/http_client.py +0 -0
  137. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/importlib.py +0 -0
  138. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/io.py +0 -0
  139. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/json.py +0 -0
  140. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/logging.py +0 -0
  141. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/numpy_random.py +0 -0
  142. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/os.py +0 -0
  143. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/pathlib.py +0 -0
  144. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/pytest.py +0 -0
  145. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/random.py +0 -0
  146. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/shutil.py +0 -0
  147. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/statistics.py +0 -0
  148. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/struct.py +0 -0
  149. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/subprocess.py +0 -0
  150. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/tempfile.py +0 -0
  151. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/textwrap.py +0 -0
  152. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/urllib.py +0 -0
  153. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/urllib_error.py +0 -0
  154. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/urllib_request.py +0 -0
  155. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/uuid.py +0 -0
  156. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/zipfile.py +0 -0
  157. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/source/zlib.py +0 -0
  158. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/string.rs +0 -0
  159. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/struct.rs +0 -0
  160. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/subprocess.rs +0 -0
  161. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/sys.rs +0 -0
  162. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/time.rs +0 -0
  163. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/typing.rs +0 -0
  164. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/unittest.rs +0 -0
  165. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/vfs.rs +0 -0
  166. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/stdlib/zlib.rs +0 -0
  167. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/string.rs +0 -0
  168. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/token.rs +0 -0
  169. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/calls.rs +0 -0
  170. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/dispatch.rs +0 -0
  171. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/host.rs +0 -0
  172. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/iteration.rs +0 -0
  173. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/namespace.rs +0 -0
  174. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/objects.rs +0 -0
  175. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm/operations.rs +0 -0
  176. {shellsim-0.1.11 → shellsim-0.1.12}/src/python/vm.rs +0 -0
  177. {shellsim-0.1.11 → shellsim-0.1.12}/src/resources.rs +0 -0
  178. {shellsim-0.1.11 → shellsim-0.1.12}/src/sandbox.rs +0 -0
  179. {shellsim-0.1.11 → shellsim-0.1.12}/src/scenario.rs +0 -0
  180. {shellsim-0.1.11 → shellsim-0.1.12}/src/scheduler.rs +0 -0
  181. {shellsim-0.1.11 → shellsim-0.1.12}/src/shell.rs +0 -0
  182. {shellsim-0.1.11 → shellsim-0.1.12}/src/telemetry.rs +0 -0
  183. {shellsim-0.1.11 → shellsim-0.1.12}/src/vfs.rs +0 -0
@@ -330,7 +330,7 @@ dependencies = [
330
330
 
331
331
  [[package]]
332
332
  name = "shellsim"
333
- version = "0.1.11"
333
+ version = "0.1.12"
334
334
  dependencies = [
335
335
  "base64",
336
336
  "crc32fast",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "shellsim"
3
- version = "0.1.11"
3
+ version = "0.1.12"
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.11
3
+ Version: 0.1.12
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.11"
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.11"
430
+ version = "0.1.12"
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.11"
5
+ version = "0.1.12"
6
6
  edition = "2021"
7
7
  description = "Python bindings for shellsim"
8
8
  license = "Apache-2.0"
@@ -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
@@ -15,6 +15,11 @@ from _asyncio import (
15
15
 
16
16
  _running_loop = None
17
17
 
18
+ ALL_COMPLETED = "ALL_COMPLETED"
19
+ FIRST_COMPLETED = "FIRST_COMPLETED"
20
+ FIRST_EXCEPTION = "FIRST_EXCEPTION"
21
+ TimeoutError = TimeoutError
22
+
18
23
 
19
24
  class CancelledError(Exception):
20
25
  pass
@@ -40,6 +45,16 @@ class _LockWait:
40
45
  self.lock = lock
41
46
 
42
47
 
48
+ class _SemaphoreWait:
49
+ def __init__(self, semaphore):
50
+ self.semaphore = semaphore
51
+
52
+
53
+ class _ConditionWait:
54
+ def __init__(self, condition):
55
+ self.condition = condition
56
+
57
+
43
58
  class _ResourceWait:
44
59
  def __init__(self, token):
45
60
  self.token = token
@@ -59,7 +74,7 @@ class _TimeoutWait:
59
74
 
60
75
 
61
76
  class Task:
62
- def __init__(self, coroutine, loop):
77
+ def __init__(self, coroutine, loop, name=None):
63
78
  if not _is_coroutine(coroutine):
64
79
  raise TypeError("a coroutine was expected")
65
80
  self._coroutine = coroutine
@@ -72,6 +87,9 @@ class Task:
72
87
  self._send_value = None
73
88
  self._started = False
74
89
  self._waiting_on = None
90
+ self._name = name
91
+ self._callbacks = []
92
+ self._cancel_requests = 0
75
93
 
76
94
  def done(self):
77
95
  return self._done
@@ -86,9 +104,45 @@ class Task:
86
104
  raise self._exception
87
105
  return self._result
88
106
 
89
- def cancel(self):
107
+ def exception(self):
108
+ if not self._done:
109
+ raise RuntimeError("task is not complete")
110
+ if self._cancelled:
111
+ raise CancelledError()
112
+ return self._exception
113
+
114
+ def get_coro(self):
115
+ return self._coroutine
116
+
117
+ def get_name(self):
118
+ return self._name
119
+
120
+ def set_name(self, name):
121
+ self._name = str(name)
122
+
123
+ def cancelling(self):
124
+ return self._cancel_requests
125
+
126
+ def uncancel(self):
127
+ if self._cancel_requests:
128
+ self._cancel_requests -= 1
129
+ return self._cancel_requests
130
+
131
+ def add_done_callback(self, callback, context=None):
132
+ if self._done:
133
+ callback(self)
134
+ else:
135
+ self._callbacks.append(callback)
136
+
137
+ def remove_done_callback(self, callback):
138
+ previous = len(self._callbacks)
139
+ self._callbacks = [item for item in self._callbacks if item is not callback]
140
+ return previous - len(self._callbacks)
141
+
142
+ def cancel(self, msg=None):
90
143
  if self._done:
91
144
  return False
145
+ self._cancel_requests += 1
92
146
  self._loop._cancel(self)
93
147
  return True
94
148
 
@@ -181,6 +235,137 @@ class Lock:
181
235
  self.release()
182
236
 
183
237
 
238
+ class Semaphore:
239
+ def __init__(self, value=1):
240
+ if value < 0:
241
+ raise ValueError("Semaphore initial value must be >= 0")
242
+ self._value = value
243
+ self._waiters = []
244
+
245
+ def locked(self):
246
+ return self._value == 0
247
+
248
+ async def acquire(self):
249
+ if self._value > 0:
250
+ self._value -= 1
251
+ return True
252
+ return await _SemaphoreWait(self)
253
+
254
+ def release(self):
255
+ if self._waiters:
256
+ task = self._waiters.pop(0)
257
+ task._loop._schedule(task, True)
258
+ else:
259
+ self._value += 1
260
+
261
+ async def __aenter__(self):
262
+ await self.acquire()
263
+ return self
264
+
265
+ async def __aexit__(self, kind, value, traceback):
266
+ self.release()
267
+
268
+
269
+ class BoundedSemaphore(Semaphore):
270
+ def __init__(self, value=1):
271
+ super().__init__(value)
272
+ self._bound_value = value
273
+
274
+ def release(self):
275
+ if not self._waiters and self._value >= self._bound_value:
276
+ raise ValueError("BoundedSemaphore released too many times")
277
+ super().release()
278
+
279
+
280
+ class Condition:
281
+ def __init__(self, lock=None):
282
+ self._lock = lock if lock is not None else Lock()
283
+ self._waiters = []
284
+
285
+ def locked(self):
286
+ return self._lock.locked()
287
+
288
+ async def acquire(self):
289
+ return await self._lock.acquire()
290
+
291
+ def release(self):
292
+ self._lock.release()
293
+
294
+ async def wait(self):
295
+ if not self.locked():
296
+ raise RuntimeError("cannot wait on un-acquired lock")
297
+ self.release()
298
+ try:
299
+ return await _ConditionWait(self)
300
+ finally:
301
+ await self.acquire()
302
+
303
+ async def wait_for(self, predicate):
304
+ result = predicate()
305
+ while not result:
306
+ await self.wait()
307
+ result = predicate()
308
+ return result
309
+
310
+ def notify(self, n=1):
311
+ if not self.locked():
312
+ raise RuntimeError("cannot notify on un-acquired lock")
313
+ for _ in range(min(n, len(self._waiters))):
314
+ task = self._waiters.pop(0)
315
+ task._loop._schedule(task, True)
316
+
317
+ def notify_all(self):
318
+ self.notify(len(self._waiters))
319
+
320
+ async def __aenter__(self):
321
+ await self.acquire()
322
+ return self
323
+
324
+ async def __aexit__(self, kind, value, traceback):
325
+ self.release()
326
+
327
+
328
+ class TaskGroup:
329
+ def __init__(self):
330
+ self._tasks = []
331
+ self._entered = False
332
+
333
+ async def __aenter__(self):
334
+ self._entered = True
335
+ return self
336
+
337
+ def create_task(self, coroutine, name=None, context=None):
338
+ if not self._entered:
339
+ raise RuntimeError("TaskGroup has not been entered")
340
+ task = create_task(coroutine, name=name)
341
+ self._tasks.append(task)
342
+ return task
343
+
344
+ async def __aexit__(self, kind, value, traceback):
345
+ self._entered = False
346
+ if kind is not None:
347
+ for task in self._tasks:
348
+ task.cancel()
349
+ await gather(*self._tasks, return_exceptions=True)
350
+ return False
351
+ if not self._tasks:
352
+ return False
353
+ done, pending = await wait(self._tasks, return_when=FIRST_EXCEPTION)
354
+ failed = []
355
+ for task in self._tasks:
356
+ if task not in done:
357
+ continue
358
+ if not task.cancelled() and task.exception() is not None:
359
+ failed.append(task.exception())
360
+ if failed:
361
+ for task in pending:
362
+ task.cancel()
363
+ await gather(*pending, return_exceptions=True)
364
+ raise failed[0]
365
+ await gather(*pending)
366
+ return False
367
+
368
+
184
369
  class StreamReader:
185
370
  def __init__(self, process, fd):
186
371
  self._process = process
@@ -299,9 +484,12 @@ class _Loop:
299
484
  self._ready = []
300
485
  self._timers = []
301
486
  self._resource_waiters = []
487
+ self._tasks = []
488
+ self._current = None
302
489
 
303
- def create_task(self, coroutine):
304
- task = Task(coroutine, self)
490
+ def create_task(self, coroutine, name=None):
491
+ task = Task(coroutine, self, name)
492
+ self._tasks.append(task)
305
493
  self._ready.append(task)
306
494
  return task
307
495
 
@@ -327,6 +515,7 @@ class _Loop:
327
515
  task._cancelled = True
328
516
  task._exception = CancelledError()
329
517
  self._wake_waiters(task)
518
+ self._run_done_callbacks(task)
330
519
  else:
331
520
  self._schedule_failure(task, CancelledError())
332
521
 
@@ -391,16 +580,27 @@ class _Loop:
391
580
  else:
392
581
  self._schedule(waiter, task._result)
393
582
 
583
+ def _run_done_callbacks(self, task):
584
+ callbacks = task._callbacks
585
+ task._callbacks = []
586
+ for callback in callbacks:
587
+ try:
588
+ callback(task)
589
+ except Exception:
590
+ pass
591
+
394
592
  def _finish(self, task, result):
395
593
  task._done = True
396
594
  task._result = result
397
595
  self._wake_waiters(task)
596
+ self._run_done_callbacks(task)
398
597
 
399
598
  def _fail(self, task, error):
400
599
  task._done = True
401
600
  task._exception = error
402
601
  task._cancelled = isinstance(error, CancelledError)
403
602
  self._wake_waiters(task)
603
+ self._run_done_callbacks(task)
404
604
 
405
605
  def _block_on_task(self, task, awaited):
406
606
  if awaited._done:
@@ -428,6 +628,10 @@ class _Loop:
428
628
  awaited.queue._getters.append(task)
429
629
  elif isinstance(awaited, _LockWait):
430
630
  awaited.lock._waiters.append(task)
631
+ elif isinstance(awaited, _SemaphoreWait):
632
+ awaited.semaphore._waiters.append(task)
633
+ elif isinstance(awaited, _ConditionWait):
634
+ awaited.condition._waiters.append(task)
431
635
  elif isinstance(awaited, _WaitFor):
432
636
  if awaited.task._done:
433
637
  self._block_on_task(task, awaited.task)
@@ -456,7 +660,9 @@ class _Loop:
456
660
  if task._done:
457
661
  continue
458
662
  task._started = True
663
+ self._current = task
459
664
  status, value = _step(task._coroutine, task._send_value)
665
+ self._current = None
460
666
  task._send_value = None
461
667
  if status == 1:
462
668
  self._finish(task, value)
@@ -473,8 +679,30 @@ def get_running_loop():
473
679
  return _running_loop
474
680
 
475
681
 
476
- def create_task(coroutine):
477
- return get_running_loop().create_task(coroutine)
682
+ def create_task(coroutine, name=None, context=None):
683
+ return get_running_loop().create_task(coroutine, name)
684
+
685
+
686
+ def ensure_future(awaitable):
687
+ return _ensure_task(awaitable)
688
+
689
+
690
+ def current_task(loop=None):
691
+ if loop is None:
692
+ loop = get_running_loop()
693
+ return loop._current
694
+
695
+
696
+ def all_tasks(loop=None):
697
+ if loop is None:
698
+ loop = get_running_loop()
699
+ return {task for task in loop._tasks if not task.done()}
700
+
701
+
702
+ def _ensure_task(awaitable):
703
+ if isinstance(awaitable, Task):
704
+ return awaitable
705
+ return create_task(awaitable)
478
706
 
479
707
 
480
708
  async def sleep(delay, result=None):
@@ -483,12 +711,7 @@ async def sleep(delay, result=None):
483
711
 
484
712
 
485
713
  async def gather(*coroutines, return_exceptions=False):
486
- tasks = []
487
- for coroutine in coroutines:
488
- if isinstance(coroutine, Task):
489
- tasks.append(coroutine)
490
- else:
491
- tasks.append(create_task(coroutine))
714
+ tasks = [_ensure_task(coroutine) for coroutine in coroutines]
492
715
  results = []
493
716
  for task in tasks:
494
717
  try:
@@ -501,15 +724,100 @@ async def gather(*coroutines, return_exceptions=False):
501
724
 
502
725
 
503
726
  async def wait_for(awaitable, timeout):
504
- if isinstance(awaitable, Task):
505
- task = awaitable
506
- else:
507
- task = create_task(awaitable)
727
+ task = _ensure_task(awaitable)
508
728
  if timeout is None:
509
729
  return await task
510
730
  return await _WaitFor(task, timeout)
511
731
 
512
732
 
733
+ def _wait_condition(tasks, return_when):
734
+ done = {task for task in tasks if task.done()}
735
+ if return_when == FIRST_COMPLETED:
736
+ return bool(done)
737
+ if return_when == FIRST_EXCEPTION:
738
+ for task in done:
739
+ if not task.cancelled() and task.exception() is not None:
740
+ return True
741
+ return len(done) == len(tasks)
742
+ return len(done) == len(tasks)
743
+
744
+
745
+ async def wait(awaitables, timeout=None, return_when=ALL_COMPLETED):
746
+ if return_when not in (ALL_COMPLETED, FIRST_COMPLETED, FIRST_EXCEPTION):
747
+ raise ValueError("invalid return_when value")
748
+ tasks = []
749
+ for awaitable in awaitables:
750
+ if awaitable not in tasks:
751
+ tasks.append(awaitable)
752
+ if not tasks:
753
+ raise ValueError("Set of Tasks/Futures is empty")
754
+ for task in tasks:
755
+ if not isinstance(task, Task):
756
+ raise TypeError("Passing coroutines is forbidden, use tasks explicitly")
757
+ changed = Event()
758
+
759
+ def task_completed(task):
760
+ changed.set()
761
+
762
+ observed = [task for task in tasks if not task.done()]
763
+ for task in observed:
764
+ task.add_done_callback(task_completed)
765
+
766
+ async def wait_until_ready():
767
+ while not _wait_condition(tasks, return_when):
768
+ changed.clear()
769
+ if _wait_condition(tasks, return_when):
770
+ break
771
+ await changed.wait()
772
+
773
+ try:
774
+ if timeout is None:
775
+ await wait_until_ready()
776
+ else:
777
+ try:
778
+ await wait_for(wait_until_ready(), timeout)
779
+ except TimeoutError:
780
+ pass
781
+ finally:
782
+ for task in observed:
783
+ task.remove_done_callback(task_completed)
784
+ done = {task for task in tasks if task.done()}
785
+ return done, set(tasks) - done
786
+
787
+
788
+ def as_completed(awaitables, timeout=None):
789
+ tasks = []
790
+ for awaitable in awaitables:
791
+ task = _ensure_task(awaitable)
792
+ if task not in tasks:
793
+ tasks.append(task)
794
+ completed = Queue()
795
+ deadline = None
796
+ if timeout is not None:
797
+ deadline = _monotonic_ns() + int(timeout * 1000000000)
798
+
799
+ def task_completed(task):
800
+ completed.put_nowait(task)
801
+
802
+ for task in tasks:
803
+ task.add_done_callback(task_completed)
804
+
805
+ async def next_result():
806
+ if deadline is None:
807
+ task = await completed.get()
808
+ else:
809
+ remaining = (deadline - _monotonic_ns()) / 1000000000
810
+ task = await wait_for(completed.get(), max(0, remaining))
811
+ return task.result()
812
+
813
+ for task in tasks:
814
+ yield next_result()
815
+
816
+
817
+ async def shield(awaitable):
818
+ return await _ensure_task(awaitable)
819
+
820
+
513
821
  async def create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None,
514
822
  cwd=None, env=None, start_new_session=False, limit=65536):
515
823
  if limit <= 0:
@@ -62,6 +62,36 @@ fn validate_array_layout(layout: &PyArrayLayout, storage_len: usize) -> PyResult
62
62
  Ok(())
63
63
  }
64
64
 
65
+ fn wait_reason_ready(
66
+ interp: &crate::interp::Interp,
67
+ reason: &crate::scheduler::WaitReason,
68
+ now: u64,
69
+ ) -> bool {
70
+ use crate::scheduler::WaitReason;
71
+
72
+ match reason {
73
+ WaitReason::Timer(deadline) => *deadline <= now,
74
+ WaitReason::InputReadable(description) => interp.descriptors.input_readable(*description),
75
+ WaitReason::PipeReadable(pipe) => interp.descriptors.pipe_readable(*pipe),
76
+ WaitReason::PipeWritable(pipe) => interp.descriptors.pipe_writable(*pipe),
77
+ WaitReason::Child(pid) | WaitReason::ChildActivity(pid) => matches!(
78
+ interp.processes.get(*pid).map(|record| record.status),
79
+ Some(crate::process::ProcessStatus::Exited(_))
80
+ ),
81
+ WaitReason::ChildDeadline(pid, deadline)
82
+ | WaitReason::ChildActivityDeadline(pid, deadline) => {
83
+ *deadline <= now
84
+ || matches!(
85
+ interp.processes.get(*pid).map(|record| record.status),
86
+ Some(crate::process::ProcessStatus::Exited(_))
87
+ )
88
+ }
89
+ WaitReason::Any(reasons) => reasons
90
+ .iter()
91
+ .any(|reason| wait_reason_ready(interp, reason, now)),
92
+ }
93
+ }
94
+
65
95
  impl PyRuntime for Vm<'_> {
66
96
  fn reserve_memory(&mut self, bytes: usize) -> PyResult<()> {
67
97
  self.reserve_result(bytes).map_err(PyError::resource_error)
@@ -1766,6 +1796,12 @@ impl PyRuntime for Vm<'_> {
1766
1796
  ));
1767
1797
  }
1768
1798
  let now = self.interp.clock.monotonic_ns();
1799
+ if reasons
1800
+ .iter()
1801
+ .any(|reason| wait_reason_ready(self.interp, reason, now))
1802
+ {
1803
+ return Ok(());
1804
+ }
1769
1805
  self.execution
1770
1806
  .async_timer_deadlines
1771
1807
  .retain(|deadline| *deadline > now);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes