Skip to content

_thread: initialize local subclasses per thread - #8353

Merged
youknowone merged 1 commit into
RustPython:mainfrom
kyokuping:codex/thread-local-init
Jul 26, 2026
Merged

youknowone merged 1 commit into
RustPython:mainfrom
kyokuping:codex/thread-local-init

Conversation

@kyokuping

@kyokuping kyokuping commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Codex:gpt-5.6

Summary

  • reject initialization arguments for _thread._local when no custom __init__ is provided
  • preserve positional and keyword arguments and rerun subclass __init__ when a thread first accesses the local object
  • remove a newly created thread-local dictionary if initialization fails, allowing later access to retry cleanly
  • add GC traversal and clearing for saved initialization arguments and per-thread dictionaries
  • enable the corresponding ThreadLocalTest argument and cycle-collection tests

Summary by CodeRabbit

  • Bug Fixes
    • Fixed _thread.local so constructor arguments are correctly applied to each thread’s instance state.
    • Added checks to reject initialization arguments when the class doesn’t define its own custom initializer.
    • Prevented failed _thread.local construction from leaving partial per-thread state, and improved garbage-collection/cleanup behavior to avoid stale data.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: db30feb9-9157-4ed2-b1a0-cd9069b25825

📥 Commits

Reviewing files that changed from the base of the PR and between da901dd and 7653689.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_threading_local.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/stdlib/_thread.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/stdlib/_thread.rs

📝 Walkthrough

Walkthrough

The _thread._local implementation now stores per-thread dictionaries and initialization arguments in traversable state, supports custom initialization during first access, cleans up failed initialization, and validates constructor arguments.

Changes

Thread-local initialization

Layer / File(s) Summary
Local state storage and traversal
crates/vm/src/stdlib/_thread.rs
LocalState stores initialization arguments and per-thread dictionaries behind PyMutex; manual traversal covers both collections, and thread cleanup removes entries from the new state.
Deferred custom initialization
crates/vm/src/stdlib/_thread.rs
First dictionary creation invokes custom __init__ with stored arguments and removes the current entry if initialization fails.
Constructor and attribute access wiring
crates/vm/src/stdlib/_thread.rs
slot_new validates constructor arguments, and __dict__ access paths use the fallible dictionary accessor.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Local
  participant LocalState
  participant CustomInit
  Caller->>Local: construct with init_args
  Local->>LocalState: store init_args
  Local->>LocalState: create current-thread dict
  Local->>CustomInit: invoke custom __init__
  CustomInit-->>Local: return success or error
  Local->>LocalState: remove dict on initialization error
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: per-thread initialization of _thread._local subclasses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] lib: cpython/Lib/sqlite3
[x] test: cpython/Lib/test/test_sqlite3 (TODO: 80)

dependencies:

  • sqlite3

dependent tests: (2 tests)

  • sqlite3: test_dbm_sqlite3 test_sqlite3

[x] lib: cpython/Lib/io.py
[x] lib: cpython/Lib/_pyio.py
[ ] test: cpython/Lib/test/test_io.py (TODO: 13)
[x] test: cpython/Lib/test/test_bufio.py
[x] test: cpython/Lib/test/test_fileio.py (TODO: 1)
[ ] test: cpython/Lib/test/test_memoryio.py (TODO: 25)

dependencies:

  • io

dependent tests: (108 tests)

  • io: test__colorize test_android test_argparse test_ast test_asyncio test_base64 test_buffer test_bufio test_builtin test_bz2 test_calendar test_cmd test_cmd_line_script test_codecs test_compile test_compileall test_compiler_assemble test_concurrent_futures test_configparser test_contextlib test_csv test_dbm_dumb test_descr test_dis test_email test_enum test_file test_fileinput test_fileio test_ftplib test_generated_cases test_getpass test_gzip test_hashlib test_http_cookiejar test_httplib test_httpservers test_importlib test_inspect test_io test_json test_largefile test_logging test_lzma test_mailbox test_marshal test_memoryio test_memoryview test_mimetypes test_minidom test_multibytecodec test_optparse test_pathlib test_pdb test_peg_generator test_pickle test_pickletools test_platform test_plistlib test_pprint test_print test_profile test_pstats test_pty test_pulldom test_pydoc test_pyexpat test_pyrepl test_quopri test_regrtest test_robotparser test_sax test_shlex test_shutil test_site test_smtplib test_socket test_socketserver test_subprocess test_support test_sys test_tarfile test_tempfile test_threadedtempfile test_timeit test_tokenize test_traceback test_types test_typing test_unittest test_univnewlines test_urllib test_urllib2 test_uuid test_wave test_webbrowser test_winconsoleio test_wsgiref test_xml_dom_xmlbuilder test_xml_etree test_xml_etree_c test_xmlrpc test_xpickle test_zipapp test_zipfile test_zipimport test_zoneinfo test_zstd

[x] lib: cpython/Lib/struct.py
[x] test: cpython/Lib/test/test_struct.py (TODO: 8)

dependencies:

  • struct

dependent tests: (179 tests)

  • struct: test_array test_buffer test_call test_compileall test_ctypes test_deque test_fcntl test_float test_gzip test_ioctl test_itertools test_logging test_math test_memoryview test_ordered_dict test_os test_pickle test_plistlib test_socket test_ssl test_str test_struct test_sys test_tools test_venv test_wave test_xml_etree_c test_xpickle test_zipfile test_zipimport test_zoneinfo
    • base64: test_base64 test_email test_gettext test_httpservers test_smtplib test_urllib2 test_urllib2_localnet test_xmlrpc
      • http.server: test_robotparser
      • logging.handlers: test_concurrent_futures test_pkgutil
      • secrets: test_secrets
      • smtplib: test_smtpnet
      • ssl: test_asyncio test_ftplib test_httplib test_imaplib test_poplib test_urllib
      • urllib.request: test_http_cookiejar test_pathlib test_pydoc test_sax test_site test_urllib2net test_urllibnet
    • ctypes: test_android test_bytes test_code test_codecs test_ctypes test_genericalias test_io test_ntpath
      • platform: test__locale test__osx_support test_asyncio test_baseexception test_builtin test_cmath test_mimetypes test_platform test_posix test_regrtest test_shutil test_strptime test_sysconfig test_time test_winreg test_wsgiref
      • webbrowser: test_webbrowser
    • dbm: test_dbm test_dbm_dumb test_dbm_sqlite3 test_shelve
    • gettext:
      • argparse: test_argparse
      • getopt: test_getopt
      • optparse: test_decimal test_optparse
    • gzip: test_fileinput test_tarfile
    • multiprocessing: test_asyncio test_concurrent_futures test_multiprocessing_main_handling test_re
      • concurrent.futures.process: test_concurrent_futures
    • pickle: test_annotationlib test_ast test_bool test_bz2 test_collections test_configparser test_coroutines test_csv test_defaultdict test_descr test_dict test_dictviews test_email test_enum test_enumerate test_exceptions test_fractions test_functools test_generators test_http_cookies test_importlib test_inspect test_ipaddress test_iter test_list test_lzma test_memoryio test_minidom test_opcache test_operator test_picklebuffer test_pickletools test_positional_only_arg test_random test_range test_set test_slice test_statistics test_string test_structseq test_super test_trace test_tuple test_turtle test_type_aliases test_type_params test_types test_typing test_unittest test_uuid test_xml_dom_minicompat test_xml_etree test_zipfile test_zlib test_zoneinfo
      • tracemalloc: test_tracemalloc
    • tarfile:
      • shutil: test_embed test_filecmp test_glob test_importlib test_largefile test_launcher test_modulefinder test_peg_generator test_py_compile test_reprlib test_string_literals test_subprocess test_support test_tempfile test_traceback test_unicode_file
    • zipfile: test_pdb test_zipapp test_zipfile test_zipfile64 test_zipimport_support
      • importlib.metadata: test_importlib
    • zipimport: test_cmd_line_script test_importlib
      • pkgutil: test_pyrepl test_runpy

[x] lib: cpython/Lib/email
[ ] test: cpython/Lib/test/test_email (TODO: 12)

dependencies:

  • email

dependent tests: (53 tests)

  • email: test_email test_http_cookiejar test_httpservers test_mailbox test_smtplib test_urllib test_urllib2 test_urllib2_localnet test_urllibnet test_zipfile
    • http.client: test_docxmlrpc test_hashlib test_ssl test_ucn test_unicodedata test_wsgiref test_xmlrpc
      • logging.handlers: test_concurrent_futures test_logging test_pkgutil
      • urllib.request: test_pathlib test_pydoc test_sax test_site test_urllib2net
    • http.server: test_robotparser
      • pydoc: test_enum
    • importlib.metadata: test_importlib test_zoneinfo
    • mailbox: test_genericalias
    • pydoc:
      • pdb: test_pdb
    • smtplib: test_smtpnet
    • urllib.request:
      • pathlib: test_ast test_dbm_sqlite3 test_ensurepip test_importlib test_json test_launcher test_pathlib test_peg_generator test_pyrepl test_runpy test_tarfile test_tempfile test_tomllib test_tools test_traceback test_unparse test_venv test_winapi test_zipapp test_zipfile test_zstd

[x] lib: cpython/Lib/threading.py
[x] lib: cpython/Lib/_threading_local.py
[ ] test: cpython/Lib/test/test_threading.py (TODO: 17)
[x] test: cpython/Lib/test/test_threadedtempfile.py
[x] test: cpython/Lib/test/test_threading_local.py (TODO: 1)

dependencies:

  • threading

dependent tests: (163 tests)

  • threading: test_android test_asyncio test_bytes test_bz2 test_code test_concurrent_futures test_context test_contextlib test_ctypes test_decimal test_docxmlrpc test_email test_enum test_external_inspection test_fork1 test_frame test_ftplib test_functools test_gc test_hashlib test_httplib test_httpservers test_imaplib test_importlib test_inspect test_io test_ioctl test_itertools test_largefile test_linecache test_logging test_memoryview test_opcache test_pathlib test_poll test_poplib test_pyrepl test_queue test_robotparser test_sched test_signal test_smtplib test_socket test_socketserver test_sqlite3 test_ssl test_subprocess test_super test_sys test_syslog test_termios test_threadedtempfile test_threading test_threading_local test_time test_urllib2_localnet test_weakref test_winreg test_wsgiref test_xmlrpc test_zstd
    • asyncio: test_asyncio test_os test_pdb test_unittest
    • bdb: test_bdb
    • concurrent.futures._base: test_concurrent_futures
    • concurrent.futures.process: test_compileall test_concurrent_futures
    • concurrent.futures.thread: test_genericalias
    • dummy_threading: test_dummy_threading
    • http.cookiejar: test_http_cookiejar test_urllib2
      • urllib.request: test_pathlib test_pydoc test_sax test_site test_urllib test_urllib2net test_urllibnet
    • importlib.util: test_asdl_parser test_ctypes test_doctest test_importlib test_pkgutil test_py_compile test_reprlib test_runpy test_zipfile test_zipimport
      • pkgutil: test_pyrepl
      • py_compile: test_argparse test_cmd_line_script test_importlib test_modulefinder test_multiprocessing_main_handling
      • pyclbr: test_pyclbr
      • sysconfig: test_c_locale_coercion test_cmd_line test_dtrace test_embed test_launcher test_osx_env test_peg_generator test_posix test_pyexpat test_regrtest test_support test_sysconfig test_tools test_venv
      • zipfile: test_shutil test_zipapp test_zipfile test_zipfile64 test_zipimport_support
    • logging: test_unittest
      • hashlib: test_hmac test_tarfile test_unicodedata
    • multiprocessing: test_fcntl test_re
    • queue: test_dummy_thread
    • subprocess: test_atexit test_audit test_ctypes test_faulthandler test_file_eintr test_gzip test_json test_msvcrt test_ntpath test_platform test_plistlib test_pyrepl test_quopri test_repl test_script_helper test_select test_sys_settrace test_tempfile test_traceback test_unittest test_utf8_mode test_wait3 test_webbrowser test_xpickle
      • ctypes.util: test_ctypes
      • ensurepip: test_ensurepip
      • platform: test__locale test__osx_support test_baseexception test_builtin test_cmath test_math test_mimetypes test_strptime
    • sysconfig:
      • trace: test_trace
    • zipfile:
      • shutil: test_filecmp test_glob test_string_literals test_unicode_file test_zoneinfo

[x] lib: cpython/Lib/ast.py
[x] lib: cpython/Lib/_ast_unparse.py
[x] test: cpython/Lib/test/test_unparse.py
[x] test: cpython/Lib/test/test_type_comments.py

dependencies:

  • ast

dependent tests: (149 tests)

  • ast: test_ast test_builtin test_compile test_compiler_codegen test_dis test_fstring test_future_stmt test_peepholer test_peg_generator test_site test_ssl test_type_comments test_ucn test_unparse
    • annotationlib: test_annotationlib test_functools test_grammar test_inspect test_reprlib test_type_annotations test_type_params test_typing
      • dataclasses: test__colorize test_copy test_ctypes test_enum test_genericalias test_patma test_pprint test_pydoc test_regrtest test_zoneinfo
      • inspect: test_abc test_argparse test_asyncgen test_buffer test_clinic test_code test_collections test_coroutines test_decimal test_generators test_monitoring test_ntpath test_operator test_posixpath test_signal test_sqlite3 test_traceback test_turtle test_types test_unittest test_yield_from test_zipimport test_zipimport_support
    • dbm.dumb: test_dbm_dumb
    • inspect:
      • bdb: test_bdb test_pdb
      • cmd: test_cmd
      • importlib.metadata: test_importlib
      • pkgutil: test_pkgutil test_pyrepl test_runpy
      • rlcompleter: test_pyrepl test_rlcompleter
      • trace: test_trace
      • xmlrpc.server: test_docxmlrpc test_xmlrpc
    • pyclbr: test_pyclbr
    • traceback: test_asyncio test_code_module test_contextlib test_contextlib_async test_dictcomps test_exceptions test_http_cookiejar test_importlib test_iter test_listcomps test_pyexpat test_setcomps test_socket test_subprocess test_sys test_threadedtempfile test_threading test_unittest test_with
      • concurrent.futures.process: test_compileall test_concurrent_futures
      • http.cookiejar: test_urllib2
      • logging: test_asyncio test_hashlib test_logging test_support test_urllib2net
      • multiprocessing: test_asyncio test_concurrent_futures test_fcntl test_memoryview test_multiprocessing_main_handling test_re
      • py_compile: test_cmd_line_script test_importlib test_modulefinder test_py_compile
      • socketserver: test_imaplib test_socketserver test_wsgiref
      • threading: test_android test_asyncio test_bytes test_bz2 test_concurrent_futures test_context test_ctypes test_email test_external_inspection test_fork1 test_frame test_ftplib test_gc test_httplib test_httpservers test_importlib test_io test_ioctl test_itertools test_largefile test_linecache test_opcache test_pathlib test_poll test_poplib test_pyrepl test_queue test_robotparser test_sched test_smtplib test_super test_syslog test_termios test_threading_local test_time test_urllib2_localnet test_weakref test_winreg test_zstd
      • timeit: test_timeit

[x] lib: cpython/Lib/shlex.py
[x] test: cpython/Lib/test/test_shlex.py (TODO: 4)

dependencies:

  • shlex

dependent tests: (10 tests)

  • shlex: test_mimetypes test_random test_regrtest test_shlex test_venv test_webbrowser
    • pdb: test_pdb
    • webbrowser:
      • pydoc: test_enum test_pydoc
      • wsgiref.simple_server: test_wsgiref

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@kyokuping
kyokuping force-pushed the codex/thread-local-init branch from 3fda9b0 to 0ba28a7 Compare July 23, 2026 10:37
@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Jul 24, 2026
@kyokuping

Copy link
Copy Markdown
Contributor Author

Hello, @ShaharNaveh 😄
While working on a small implementation to pass the thread.__init__ tests, I implemented traverse on LocalState.

I opted for HashMap for dicts since non-deterministic iteration order seems fine here. However, this triggers clippy::iter_over_hash_map, requiring #[allow(clippy::iter_over_hash_map)]. 😅

If you don't mind, I'd love to get your thoughts on this: Is adding #[allow(clippy::iter_over_hash_map)] fine here, or would you prefer switching to a deterministic map (like BTreeMap or IndexMap) to avoid suppressing the lint?

Comment thread crates/vm/src/stdlib/_thread.rs Outdated
unsafe impl Traverse for LocalState {
fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) {
self.init_args.traverse(tracer_fn);
for (_, dict) in &self.dicts {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may fix the two Clippy lints, but it may not work:

Suggested change
for (_, dict) in &self.dicts {
let mut dicts: Vec<_> = &self.dicts.values().collect();
dicts.sort();
for dict in dicts {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider using ⁠.collect()⁠, but I was worried about the cost of allocating a new Vec here. 🥲

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, let's avoid unnecessary allocation

@kyokuping
kyokuping force-pushed the codex/thread-local-init branch from 0ba28a7 to 2da62c8 Compare July 25, 2026 08:14
@kyokuping
kyokuping marked this pull request as ready for review July 25, 2026 14:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/_thread.rs (1)

928-937: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid dropping guards while LOCAL_GUARDS is borrowed.

cleanup_thread_local_data() clears LOCAL_GUARDS.borrow_mut(), and each LocalGuard::drop() removes a Python dict; the dict’s __del__() can synchronously access a different _local, creating the same guard and calling the borrowed LOCAL_GUARDS RefCell again (Already borrowed: BorrowMutError). Clone/clear just the data here or take the guard out first, like remove_current_dict() does.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_thread.rs` around lines 928 - 937, The LocalGuard::drop
cleanup can trigger __del__ while LOCAL_GUARDS is mutably borrowed, causing
reentrant BorrowMutError. Update the LocalGuard drop path used by
cleanup_thread_local_data() to remove or take the thread-local guard/data before
dropping the Python dict, following the safe ownership pattern in
remove_current_dict() so destructor execution occurs after the LOCAL_GUARDS
borrow ends.
🧹 Nitpick comments (1)
crates/vm/src/stdlib/_thread.rs (1)

939-958: 🚀 Performance & Scalability | 🔵 Trivial

Re: HashMap vs BTreeMap/IndexMap for LocalState.dicts.

Since traverse/clear only need completeness (not ordering) for GC correctness, and dicts is otherwise used purely for keyed lookup (get/entry/remove), HashMap with the O(1) average lookup is the right choice here — BTreeMap/IndexMap would only add overhead (O(log n) lookups, or an extra dependency) without a corresponding benefit. Keeping HashMap + #[allow(clippy::iter_over_hash_type)] is reasonable.

One small nit: the codebase already has precedent for the "unordered hash iteration" allowance using #[expect(clippy::iter_over_hash_type, reason = "...")] (e.g. crates/vm/src/builtins/frame.rs), which documents why order doesn't matter. Matching that style here would be slightly more consistent/self-documenting than a bare #[allow(...)].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/vm/src/stdlib/_thread.rs` around lines 939 - 958, Replace the bare
#[allow(clippy::iter_over_hash_type)] on LocalState::traverse with the
codebase’s #[expect(..., reason = "...")] style, documenting that dict traversal
order is irrelevant because GC only requires complete visitation. Keep the
HashMap implementation and traversal logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/vm/src/stdlib/_thread.rs`:
- Around line 928-937: The LocalGuard::drop cleanup can trigger __del__ while
LOCAL_GUARDS is mutably borrowed, causing reentrant BorrowMutError. Update the
LocalGuard drop path used by cleanup_thread_local_data() to remove or take the
thread-local guard/data before dropping the Python dict, following the safe
ownership pattern in remove_current_dict() so destructor execution occurs after
the LOCAL_GUARDS borrow ends.

---

Nitpick comments:
In `@crates/vm/src/stdlib/_thread.rs`:
- Around line 939-958: Replace the bare #[allow(clippy::iter_over_hash_type)] on
LocalState::traverse with the codebase’s #[expect(..., reason = "...")] style,
documenting that dict traversal order is irrelevant because GC only requires
complete visitation. Keep the HashMap implementation and traversal logic
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: ddfe89ed-cc11-4bcc-9573-874d048e779d

📥 Commits

Reviewing files that changed from the base of the PR and between 28454cc and 2da62c8.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_threading_local.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/stdlib/_thread.rs

@kyokuping
kyokuping force-pushed the codex/thread-local-init branch from da901dd to 7653689 Compare July 25, 2026 16:15

@youknowone youknowone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thank you!

@youknowone
youknowone merged commit 44e5d84 into RustPython:main Jul 26, 2026
27 checks passed
@kyokuping
kyokuping deleted the codex/thread-local-init branch August 8, 2026 05:58
youknowone pushed a commit that referenced this pull request Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants