Fix multithreading - #157
Conversation
… must now always have a TXN argument, ty cleanups.
|
root cause found, insert_database still called inherited insert function. |
In order to faciliate a lighter importing pattern, we are moving out functions that are not using domain.netex.model. This allows the multiprocessing code to import more efficiently.
With Google Antigravity I was able to find the root cause of the performance degredation of multiprocessing. Rest are improvements. 1. import domain.netex.model takes long, with spawn it is executed every time that a process is created causing significant overhead. 2. Having a multithreaded consumer that is not dependent on anly NeTEx entities won't have startup costs when spawned. The alternative is to have it not spawned, but forked too. 3. There is noticable IPC overhead, by batching the queue entries we lower this overhead. 4. By not making the MdbxStorageMP part of the forked processes, they cannot conflic. 5. By solving the the problem where insert_database was not using the multithreaded insert (BUG). We significantly improve the total outcome. 6. A netex_to_db_mp has been added, which does the most naive insert (thus great for EPIP LINE OFFER, but swiss_to_db_mp still relevant).
ue71603
left a comment
There was a problem hiding this comment.
it works when I run
python -m tools.script_runner ./tools/tool_scripts/script_regression.txt reg.log all
Do you want me to run something else?
|
is still single-treaded use on my machine. |
|
If I need some other parameters, environment, then tell me. Also, the multi-threaded version in my view is not described anywhere. and no pointers on how to use it with script_runner is provided. |
To validate the code works on your system: |
ue71603
left a comment
There was a problem hiding this comment.
problem I got with running swissinterchange:
(badger) PS D:\development\github\badger> python -m tools.script_runner ./tools/tool_scripts/script_regression.txt reg.log swissinterchange
2026-07-31 23:34:48,215 - script_runner - INFO - swissinterchange - step: 1: clean_tmp d:/aux_testing_processing/swissinterchange
2026-07-31 23:34:48,219 - script_runner - INFO - Command 'clean_tmp' executed for folder: d:/aux_testing_processing/swissinterchange
2026-07-31 23:34:48,220 - script_runner - INFO - swissinterchange - step: 2: download_input_file d:/aux_testing_processing/swissinterchange
2026-07-31 23:34:48,220 - script_runner - INFO - File exists already. Will use: d:/aux_testing_processing/swissinterchange\swiss_interchange.zip
filepaths: d:/aux_testing_processing/swissinterchange\swiss_interchange.zip
2026-07-31 23:34:48,220 - script_runner - INFO - Command 'download_input_file' executed for url: https://github.com/user-attachments/files/18870701/swiss_interchange.zip
2026-07-31 23:34:48,221 - script_runner - INFO - swissinterchange - step: 3: conv.netex_to_db_mp d:/aux_testing_processing/swissinterchange\swiss_interchange.zip d:/aux_testing_processing/swissinterchange/03.mdbx
module 'multiprocessing.context' has no attribute 'ForkProcess'
2026-07-31 23:34:57,447 - script_runner - ERROR - module 'multiprocessing.context' has no attribute 'ForkProcess'
Traceback (most recent call last):
File "D:\development\github\badger\tools\script_runner.py", line 413, in main
result = load_and_run(script_name, script_args)
File "D:\development\github\badger\tools\script_runner.py", line 75, in load_and_run
mod = importlib.import_module(module_name)
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\importlib\__init__.py", line 88, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 1026, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "D:\development\github\badger\conv\netex_to_db_mp.py", line 6, in <module>
from storage.mdbx.core.implementation_mp import MdbxStorageMP
File "D:\development\github\badger\storage\mdbx\core\implementation_mp.py", line 11, in <module>
class MdbxStorageMP(MdbxStorageQueue):
...<29 lines>...
return super().__exit__(exception_type, exception_value, exception_traceback)
File "D:\development\github\badger\storage\mdbx\core\implementation_mp.py", line 12, in MdbxStorageMP
writer: mp.context.ForkProcess
^^^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'multiprocessing.context' has no attribute 'ForkProcess'
2026-07-31 23:34:57,451 - script_runner - INFO - Execution time: 9.2 seconds for swissinterchange - step: 3: conv.netex_to_db_mp d:/aux_testing_processing/swissinterchange\swiss_interchange.zip d:/aux_testing_processing/swissinterchange/03.mdbx
2026-07-31 23:34:57,452 - script_runner - ERROR - Script conv.netex_to_db_mp returned an error. Terminating the block of scripts: swissinterchange
(badger) PS D:\development\github\badger>
with the unit tests I get:
and
Traceback (most recent call last):
File "C:\Program Files\JetBrains\PyCharm Community Edition 2025.1.1\plugins\python-ce\helpers\pydev\pydevd.py", line 1570, in _exec
pydev_imports.execfile(file, globals, locals) # execute the script
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\JetBrains\PyCharm Community Edition 2025.1.1\plugins\python-ce\helpers\pydev\_pydev_imps\_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:/Program Files/JetBrains/PyCharm Community Edition 2025.1.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py", line 38, in <module>
sys.exit(main(argv=args, module=None, testRunner=unittestpy.TeamcityTestRunner,
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
buffer=not JB_DISABLE_BUFFERING))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\unittest\main.py", line 103, in __init__
self.parseArgs(argv)
~~~~~~~~~~~~~~^^^^^^
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\unittest\main.py", line 142, in parseArgs
self.createTests()
~~~~~~~~~~~~~~~~^^
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\unittest\main.py", line 153, in createTests
self.test = self.testLoader.loadTestsFromNames(self.testNames,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
self.module)
^^^^^^^^^^^^
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\unittest\loader.py", line 207, in loadTestsFromNames
suites = [self.loadTestsFromName(name, module) for name in names]
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
File "C:\Users\Gm\AppData\Local\Programs\Python\Python313\Lib\unittest\loader.py", line 137, in loadTestsFromName
module = __import__(module_name)
ValueError: Empty module name
python-BaseException
|
@ue71603 I'll test windows later. |
It fails afterwards, because the script_runner fix is in a different branch, but this gets the multi-core going. MistralAI said to do it like that.
|
some small eror (but perhaps this is more mypy that is not happy). context is defined in the outer function. And it shows how windows get happy to load. |
| if sys.platform == 'win32': | ||
| context = mp.get_context('spawn') | ||
| else: | ||
| context = mp.get_context('fork') | ||
| writer: context.Process |
There was a problem hiding this comment.
@ue71603 This is invalid, because you can't setup the variable here.
|
@ue71603 please test again. Unittests work for me on Windows now |
|
uv run -m unittest |
|
I changed swiss4 You still tryy to do a fork, this does not work for windows: pushed a fix |
(not fully related, but it needs to be done anyhow) and doesn't affect your code.
|
@ue71603 due to how windows works, you don't get the benefit of a fork. Due to how python with spawn works, you have to import all code again. The future where Python would support actual multithreading (python without the global interpreter lock) looks promissing, but not enabled by default. Hence only python >=3.13 versions explicitly removed it at compilation time will benefit. We can and should put migration to threads on the backlog obviously. |

Fix #147