Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to Pyodide 0.23 #1347

Merged
merged 24 commits into from
May 24, 2023
Merged

Conversation

JeffersGlass
Copy link
Member

@JeffersGlass JeffersGlass commented Apr 1, 2023

Description

This PR bumps the Pyodide version from 0.22.1 to 0.23.2. See the pyodide changelog for a detailed list of changes.

Closes #1346

Changes

Bumped pyodide version in npm, default Interpreter loading paths, tests, and documentation.

Comments

It would be nice to expose some of the reduced loading-size options to PyScript users. I suppose they can use [interpreters] to specify the smaller pyc version directly, though it might be nice to have that as a separate key in py-config. (Pyodide 3701)

Possibly exposing the new stdLibURL parameter of loadPyodide would be good as well (Pyodide 3670).

@marimeireles there's been some updates to the isCallable interface (now I think the method is instanceof pyodide.ffi.PyCallable), which might help with some of the things you were discussing in #1336?

@FabioRosado & @marimeireles runPython and runPythonAsync now take a locals argument, but I think with the new event work that's no longer a critical feature, yeah? (Pyodide 3618)

Checklist

  • All tests pass locally
  • I have updated docs/changelog.md
  • I have created documentation for this(if applicable)

@JeffersGlass JeffersGlass added the tag: pyodide Related to issues with pyodide label Apr 1, 2023
Copy link
Contributor

@hoodmane hoodmane left a comment

Choose a reason for hiding this comment

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

In a followup, we could switch to injecting the default URL (in remote_interpreter.ts) and the version in the tests from package.json. Ideally we should say the version:

  • one time in the changelog
  • one time in the docs
  • one time in environment.yml
  • and one time in package.json

so that there are only four edits needed to bump the version.

docs/development/developing.md Outdated Show resolved Hide resolved
docs/development/developing.md Outdated Show resolved Hide resolved
pyscriptjs/environment.yml Outdated Show resolved Hide resolved
pyscriptjs/package.json Outdated Show resolved Hide resolved
pyscriptjs/src/remote_interpreter.ts Show resolved Hide resolved
pyscriptjs/src/remote_interpreter.ts Outdated Show resolved Hide resolved
pyscriptjs/tests/integration/test_py_config.py Outdated Show resolved Hide resolved
@hoodmane
Copy link
Contributor

hoodmane commented Apr 1, 2023

Thanks @JeffersGlass!

@JeffersGlass
Copy link
Member Author

In a followup, we could switch to injecting the default URL (in remote_interpreter.ts) and the version in the tests from package.json. Ideally we should say the version:

  • one time in the changelog
  • one time in the docs
  • one time in environment.yml
  • and one time in package.json

so that there are only four edits needed to bump the version.

I love this idea - I've turned it into issue #1349 so we don't lose track of it when this gets merged.

@JeffersGlass
Copy link
Member Author

Of the tests that failed in the most recent commit (403eae0), five of them seem to be timing/timeout issues: test_bokeh, test_numpy_canvas_fractals, test_folium, test_panel_deckgl, and test_panel_stream. They have out largest number of external packages and largest workload, so not entirely surprised. Could see the wisdom of doing single-test reruns as suggested here.

test_pyrepl_exec_hooks is also flaky as discovered elsewhere - seems to be a timing issue in that test - the output is consistently as expected, but possibly it's getting output prior to the test's wait_for_console, so it's stuck waiting for an output that's already occurred.

test_panel_kmeans is the only test that seems to be actually failing in a meaningful way, will dig in a bit there.

@antocuni
Copy link
Contributor

antocuni commented Apr 4, 2023

test_pyrepl_exec_hooks is also flaky as discovered elsewhere - seems to be a timing issue in that test - the output is consistently as expected, but possibly it's getting output prior to the test's wait_for_console, so it's stuck waiting for an output that's already occurred.

I plan to write an improved version of wait_for_console which works even in case the output is already in the console. I'm doing it as part of #1333 but if you think it's useful also for this, I am happy to add it on its own PR.

Also, unrelated note: we talked multiple times about declaring that each pyscript version support one and only one pyodide version. Maybe this is the right occasion to clean up the code and kill all the logic whose only goal is to support multiple ones?

@antocuni
Copy link
Contributor

antocuni commented Apr 4, 2023

I plan to write an improved version of wait_for_console which works even in case the output is already in the console

ok, I did it in PR #1363 :)

@JeffersGlass
Copy link
Member Author

#1363 has solved the repl_test issue! Huzzah!

But now something weird is happening... tests are crashing both in CI and when run locally, when running threaded. They seem to be the same collection of tests that were taking longer before, but perhaps not. Have not dug in very far yet.

@antocuni
Copy link
Contributor

But now something weird is happening... tests are crashing both in CI and when run locally, when running threaded. They seem to be the same collection of tests that were taking longer before, but perhaps not. Have not dug in very far yet.

maybe for some reason they use more memory and thus we get an OOM?
I have looked at the logs on CI but I could not see anything useful there: do you see anything useful when you run them locally?
If you are on linux, you can determine whether the process was killed by an OOM by looking at dmesg immediately after the end.

@JeffersGlass
Copy link
Member Author

JeffersGlass commented Apr 13, 2023

Still gathering data on this, please forgive the lengthy brain dump.

@antocuni the issue doesn't appear to be memory related; at least locally where I can monitor it, memory usage is nominal.

I perhaps mispoke earlier - tests are not crashing, they are hanging. In that they never actually finish. I've run the integration tests 10 times waiting at least 5 minutes after the last printed line (and in one case, over 2.5 hours when I walked away), and the tests do not complete.

Tests do all successfully complete when run not-threaded, so I suspect it's some interaction with pytest-xdist or similar. I tried running with --numprocesses 4 (instead of auto) to use less cores than my local machine has, but that didn't appear to make a difference.

I've saved the test output for these runs and processed it below - test names on the left, results on the right; you may need to scroll right to see the results. For brevity (lol), I've only included the tests that had at least one non-success.

The results key is:

  • _ => Test Passed
  • F => Test Failed
  • E => Error
  • U => Test started, but never completed
  • : => Test in test suite, but never started

Starting from run 6, I included the pytest-random-order plugin with --random-order-bucket=global, hence why the failues seem more "spread out" over the latter half of the tests. While the tests that fail or fail to finish appear to be some of our long-running ones (panel_kmeans, bokeh, etc), there's no smoking gun that seems to be the obvious culprit of why the tests always hang.

tests/integration/test_01_basic.py::TestBasic::test_execution_in_order[chromium]                                                  ______F:__
tests/integration/test_01_basic.py::TestBasic::test_getPySrc_returns_source_code[chromium]                                        _____:_UF_
tests/integration/test_01_basic.py::TestBasic::test_no_python_wheel[chromium]                                                     _______:F_
tests/integration/test_01_basic.py::TestBasic::test_packages[chromium]                                                            ________F_
tests/integration/test_01_basic.py::TestBasic::test_py_attribute_without_id[chromium]                                             ______F___
tests/integration/test_02_display.py::TestOutput::test_console_line_break[chromium]                                               ______F___
tests/integration/test_02_display.py::TestOutput::test_display_multiple_values[chromium]                                          _______FF_
tests/integration/test_02_display.py::TestOutput::test_image_display[chromium]                                                    _____F____
tests/integration/test_02_display.py::TestOutput::test_image_renders_correctly[chromium]                                          ______U_F_
tests/integration/test_02_display.py::TestOutput::test_implicit_target_from_a_different_tag[chromium]                             ______F___
tests/integration/test_02_display.py::TestOutput::test_text_HTML_and_console_output[chromium]                                     _____F____
tests/integration/test_03_element.py::TestElement::test_element_add_multiple_class[chromium]                                      _______F__
tests/integration/test_03_element.py::TestElement::test_element_add_single_class[chromium]                                        ________F_
tests/integration/test_03_element.py::TestElement::test_element_clear_input[chromium]                                             _____F____
tests/integration/test_03_element.py::TestElement::test_element_id[chromium]                                                      ______F___
tests/integration/test_03_element.py::TestElement::test_element_remove_multiple_classes[chromium]                                 _____F____
tests/integration/test_async.py::TestAsync::test_asyncio_create_task[chromium]                                                    _____F_F__
tests/integration/test_async.py::TestAsync::test_asyncio_gather[chromium]                                                         _____F____
tests/integration/test_plugins.py::TestPlugin::test_fetch_js_no_export[chromium]                                                  _____FF___
tests/integration/test_plugins.py::TestPlugin::test_fetch_python_plugin[chromium]                                                 _____EE_F_
tests/integration/test_py_config.py::TestConfig::test_interpreter_config[chromium]                                                FFFFFFFF__
tests/integration/test_py_config.py::TestConfig::test_paths_from_packages[chromium]                                               ______F___
tests/integration/test_py_config.py::TestConfig::test_runtime_still_works_but_shows_deprecation_warning[chromium]                 FFFFFFF_F_
tests/integration/test_py_repl.py::TestPyRepl::test_execute_code_typed_by_the_user[chromium]                                      _______F__
tests/integration/test_py_repl.py::TestPyRepl::test_execute_on_shift_enter[chromium]                                              ________F_
tests/integration/test_py_repl.py::TestPyRepl::test_repl_load_content_from_src[chromium]                                          _____F____
tests/integration/test_py_repl.py::TestPyRepl::test_repl_loads[chromium]                                                          ______F___
tests/integration/test_py_repl.py::TestPyRepl::test_repl_src_change[chromium]                                                     ______FF__
tests/integration/test_py_repl.py::TestPyRepl::test_show_last_expression[chromium]                                                ________F_
tests/integration/test_py_repl.py::TestPyRepl::test_show_last_expression_with_output[chromium]                                    _____F____
tests/integration/test_py_terminal.py::TestPyTerminal::test_two_terminals[chromium]                                               _______F__
tests/integration/test_splashscreen.py::TestSplashscreen::test_autoshow_and_autoclose[chromium]                                   _____F____
tests/integration/test_stdio_handling.py::TestOutputHandling::test_stdio_output_attribute_change[chromium]                        _____FF___
tests/integration/test_stdio_handling.py::TestOutputHandling::test_stdio_target_element_id_change[chromium]                       _______F__
tests/integration/test_stdio_handling.py::TestOutputHandling::test_targeted_stdio_solo[chromium]                                  ________F_
tests/integration/test_zz_examples.py::TestExamples::test_altair[chromium]                                                        FFFFFFF___
tests/integration/test_zz_examples.py::TestExamples::test_bokeh[chromium]                                                         FUUFF_E_E_
tests/integration/test_zz_examples.py::TestExamples::test_bokeh_interactive[chromium]                                             ____F____F
tests/integration/test_zz_examples.py::TestExamples::test_folium[chromium]                                                        FFFF_F_E__
tests/integration/test_zz_examples.py::TestExamples::test_hello_world[chromium]                                                   ____FF____
tests/integration/test_zz_examples.py::TestExamples::test_matplotlib[chromium]                                                    FFFF___FF_
tests/integration/test_zz_examples.py::TestExamples::test_numpy_canvas_fractals[chromium]                                         EFEF_F_E__
tests/integration/test_zz_examples.py::TestExamples::test_panel[chromium]                                                         F_FFF_____
tests/integration/test_zz_examples.py::TestExamples::test_panel_deckgl[chromium]                                                  FFFE:_FF:_
tests/integration/test_zz_examples.py::TestExamples::test_panel_kmeans[chromium]                                                  :::FFFUUUF
tests/integration/test_zz_examples.py::TestExamples::test_panel_stream[chromium]                                                  FFF:FFFFF_
tests/integration/test_zz_examples.py::TestExamples::test_repl2[chromium]                                                         F_FFFU_EFU
tests/integration/test_zz_examples.py::TestExamples::test_repl[chromium]                                                          __FU______
tests/integration/test_zz_examples.py::TestExamples::test_simple_clock[chromium]                                                  ______F__F
tests/integration/test_zz_examples.py::TestExamples::test_webgl_raycaster_index[chromium]                                         __F:______
tests/integration/test_zzz_docs_snippets.py::TestDocsSnippets::test_guides_asyncio[chromium]                                      F_U_______
tests/integration/test_zzz_docs_snippets.py::TestDocsSnippets::test_tutorials_py_click[chromium]                                  ___F______
tests/integration/test_zzz_docs_snippets.py::TestDocsSnippets::test_tutorials_py_config_fetch[chromium]                           _______F__
tests/integration/test_zzz_docs_snippets.py::TestDocsSnippets::test_tutorials_py_config_interpreter[chromium]                     U__F______
tests/integration/test_zzz_docs_snippets.py::TestDocsSnippets::test_tutorials_writing_to_page[chromium]                           F_________

And finally, just because this wasn't long enough: when I kill the tests via a keyboard interrupt, I've been saving the tracebacks. They all look generally like this, if it means anything to anyone:

^C^CException ignored in: <function WeakSet.__init__.<locals>._remove at 0x7f4baddcd4e0>
Traceback (most recent call last):
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/_weakrefset.py", line 39, in _remove
    def _remove(item, selfref=ref(self)):

KeyboardInterrupt: 
^CTraceback (most recent call last):
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/main.py", line 268, in wrap_session
    session.exitstatus = doit(config, session) or 0
                         ^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/main.py", line 322, in _main
    config.hook.pytest_runtestloop(session=session)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/xdist/dsession.py", line 117, in pytest_runtestloop
    self.loop_once()
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/xdist/dsession.py", line 131, in loop_once
    eventcall = self.queue.get(timeout=2.0)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/queue.py", line 180, in get
    self.not_empty.wait(remaining)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/threading.py", line 324, in wait
    gotit = waiter.acquire(True, timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/main.py", line 282, in wrap_session
    config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/terminal.py", line 832, in pytest_keyboard_interrupt
    self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 666, in getrepr
    return fmt.repr_excinfo(self)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 926, in repr_excinfo
    reprtraceback = self.repr_traceback(excinfo_)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 867, in repr_traceback
    reprentry = self.repr_traceback_entry(entry, einfo)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 810, in repr_traceback_entry
    source = self._getentrysource(entry)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 718, in _getentrysource
    source = entry.getsource(self.astcache)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/code.py", line 260, in getsource
    astnode, _, end = getstatementrange_ast(
                      ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/_code/source.py", line 185, in getstatementrange_ast
    astnode = ast.parse(content, "source", "exec")
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/ast.py", line 50, in parse
    return compile(source, filename, mode, flags,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/bin/pytest", line 11, in <module>
    sys.exit(console_main())
             ^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/config/__init__.py", line 187, in console_main
    code = main()
           ^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/config/__init__.py", line 164, in main
    ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/main.py", line 315, in pytest_cmdline_main
    return wrap_session(config, _main)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/main.py", line 303, in wrap_session
    config.hook.pytest_sessionfinish(
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 55, in _multicall
    gen.send(outcome)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/_pytest/terminal.py", line 798, in pytest_sessionfinish
    outcome.get_result()
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/xdist/dsession.py", line 87, in pytest_sessionfinish
    nm.teardown_nodes()
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/xdist/workermanage.py", line 81, in teardown_nodes
    self.group.terminate(self.EXIT_TIMEOUT)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/execnet/multi.py", line 215, in terminate
    safe_terminate(
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/execnet/multi.py", line 311, in safe_terminate
    reply.get()
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/execnet/gateway_base.py", line 206, in get
    self.waitfinish(timeout)
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/site-packages/execnet/gateway_base.py", line 213, in waitfinish
    if not self._result_ready.wait(timeout):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/threading.py", line 622, in wait
    signaled = self._cond.wait(timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jglass/Documents/pyscript/pyscriptjs/env/lib/python3.11/threading.py", line 320, in wait
    waiter.acquire()
KeyboardInterrupt

@JeffersGlass
Copy link
Member Author

If anyone else wants to try their hand at this, feel free - I'd like to prioritize the event handlers PR (and to a lesser extent, xterm).

@JeffersGlass JeffersGlass changed the title Upgrade to Pyodide 0.23.0 Upgrade to Pyodide 0.23 May 10, 2023
@antocuni
Copy link
Contributor

Tests do all successfully complete when run not-threaded, so I suspect it's some interaction with pytest-xdist or similar. I tried running with --numprocesses 4 (instead of auto) to use less cores than my local machine has, but that didn't appear to make a difference.

I'm not too surprised by this to be honest. In my experience, in all the projects in which I tried to use pytest-xdist, I always ended up in a situation like this.

For all I know, we might also decide to just not use pytest-xdist and be happy, although I'm perfectly aware that faster tests are a good thing.

But I'm still confused and I'm not 100% to have correctly understood what's happening. Please let me know whether the following sentences are correct or now:

  1. if you run test locally sequentially, they 100% pass
  2. if you run test locally with pytest-xdist, they 100% pass
  3. on CI and sequentially, they 100% pass
  4. on CI with pytest-xdist, they have the random failure/hanging that you described above.

If so, it would be interesting to see the tracebacks for those tests which FAIL in case (4)

@JeffersGlass
Copy link
Member Author

@antocuni that is not quite the situation I have been seeing, and also my commits this morning have changed things a bit:

  1. Running tests locally and sequentially, the tests 100% pass
  2. Running tests locally with pytest-xdist, the tests experience the random failure/hanging
  3. Running tests in CI and sequentially, the tests 100% pass (for instance)
  4. Running the tests on CI with pytest-xdist:
    • Previous to today the tests were randomly failing hanging (like in this test run from commit 87ca3cf).
    • After this morning when I bumped up to Pyodide 0.23.2 and fixed an issue with the panel_kmeans (with new panel API), the test passed?? I am not convinced... will be pushing a couple of cleanup commits and seeing what happens with the tests in CI. (Also, the docs build crashed on this morning's last commit 92e385c, but I think that's because GitHub is having issues today)

Watching memory usage more closely while the tests are running in parallel locally, it seems like there is indeed an Out-of-Memory issue - at least running locally, near the end of tests, my local machine runs out of memory, then swap rapidly fills up, then the tests crash/hang. The largest memory hogs are the tests in test_zz_examples - omitting that file, the tests seem to pass (3 of 3 tries so far) even with pytest-xdist.

@antocuni
Copy link
Contributor

@antocuni that is not quite the situation I have been seeing, and also my commits this morning have changed things a bit:

  1. Running tests locally and sequentially, the tests 100% pass
  2. Running tests locally with pytest-xdist, the tests experience the random failure/hanging
  3. Running tests in CI and sequentially, the tests 100% pass (for instance)
  4. Running the tests on CI with pytest-xdist:

ok that's good. So this mean that the problem is not CI vs local, it's xdist vs sequential.

If it's really the OOM-killer, I'm not surprised to see random tests hanging. For each test, there are at least three processes involved (possibly more):

  1. the main pytest runner
  2. the pytest-xdist process running the test
  3. the chromium instance controlled by playwright

If (2) or (3) gets killed, it's totally possible that the rest of the system becomes confused and hangs.

Watching memory usage more closely while the tests are running in parallel locally, it seems like there is indeed an Out-of-Memory issue - at least running locally, near the end of tests, my local machine runs out of memory, then swap rapidly fills up, then the tests crash/hang. The largest memory hogs are the tests in test_zz_examples - omitting that file, the tests seem to pass (3 of 3 tries so far) even with pytest-xdist.

If you are on linux, you can look at dmesg to see whether there was an OOM-killer event. See here for an example of how it looks like.

It's totally possible that running test_zz_examples in parallel takes too much memory, because some of them are probably very heavy. I suggest the following:

  1. first, comment out test_zz_examples.py tests and run them a couple of times: if our theory is correct, you should see a green build reliably
  2. if (1) is true, then we can think about ways to tell pytest-xdist to run those tests sequentially instead of in parallel (the easiest is probably to have two pytest jobs, one which runs everything minus examples with xdist, and one which runs just examples without xdist)

@JeffersGlass
Copy link
Member Author

JeffersGlass commented May 12, 2023

first, comment out test_zz_examples.py tests and run them a couple of times: if our theory is correct, you should see a green build reliably

I've tried out running the tests with test_zz_examples.py removed in this fork. The tests passed 3 of 4 times, and the one time it didn't the one test that failed was test_py_script_src_not_found. I think that's flaky in an unrelated way, the test output here is one I've seen before, I think a timing issue with that test.

I will break out the integration tests into two separate runs as you say, I think that's probably the smoothest way forward. It does sound like our GitHub action runner is getting beefed up - I could still see a world where we run the examples tests in Parallel in CI if that happens.

@JeffersGlass
Copy link
Member Author

JeffersGlass commented May 15, 2023

CI is green again and this is once again ready for review.

@antocuni I refactored test_pyscript_src_not_found to remove the with pytest.raises(PageErrors) - if you look at the output of these successful CI tests vs these failed CI tests (between which nothing changed in the code), it seems there is a timing issue with using that syntax to wrap run_pyscript. It seems it's possible that the js error will not be raised prior to run_pyscript() finishing. I've added the check_js_errors parameters to run_pyscript itself and deferred looking for errors until later in the test. The relevant changes are in this commit.

// for which the signature of `loadPackage` accepts the above params as args i.e.
// the call uses `logger.info.bind(logger), logger.info.bind(logger)`.
const messageCallback = logger.info.bind(logger) as typeof logger.info;
if (this.interpreter.version.startsWith('0.22')) {
if (Number(this.interpreter.version.split('.')[1]) >= 22) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: a funny topic here ... JavaScript already understands semver in strings by design:

'0.23.2' > '0.22'; // true
'0.22' > '0.22'; // false
'0.22.1' > '0.22'; // true
'0.23' > '0.22.100001'; // true

accordingly .. .do we really need to do this dance that obviously doesn't scale / work moving forward with major versions too?

Copy link
Contributor

Choose a reason for hiding this comment

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

What about 0.23.10 vs 0.23.9. This is generally what causes trouble when comparing versions lexicographically.

Copy link
Contributor

@WebReflection WebReflection May 16, 2023

Choose a reason for hiding this comment

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

you are right, that fails as 0.23.09 should be on the right side ... good call, but here we're targeting a minor and any 0.23.XX will always be greater than 0.22.X so when it's minor versions we're after, the current dance looks unnecessary to me ... if it was about patches, the patch that introduced breaking changes was bad and yet we'd be good if minor or major is meant.

edit

in this case we should use "0.22" > "0.21" unless the 0.22.X patch broke the API or expectations in which case my hint wouldn't work (but also wouldn't the current proposed change) but I think 0.22.X should not have changed the API there ... but I guess that's another story and here we're still looking for >= 0.22

Copy link
Member Author

Choose a reason for hiding this comment

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

The breaking change in the API that this accounts for is in 0.22.0 (the minor-version release), but it does feel odd to me to do string-wise comparison on what are ultimately string-represented numbers. That said, I don't mind doing this in a Javascript-y way. Will make the change.

Copy link
Contributor

Choose a reason for hiding this comment

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

the thing is that once we have a major this code won't work anymore while using "1.0" > "0.21" will, so that there's nothing to change from our side in the future if we just use the current version and compare it against "0.21" as string.

it's quirky but less maintenance and logic or surprises prone.

Copy link
Contributor

Choose a reason for hiding this comment

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

But you do have the problem that "0.100.0" < "0.22"...

Copy link
Contributor

Choose a reason for hiding this comment

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

with our time to release I don't see that as a real-world issue to consider + I am not a big fan of 0.100 releases ... not even React will reach that 😅 ... also I believe whenever that happens this code won't be needed or exist anymore, I am just a bit pragmatic here but no hard feelings with using current logic.

self.goto("examples/panel_kmeans.html")
self.wait_for_pyscript(timeout=90 * 1000)
self.wait_for_pyscript(timeout=120 * 1000)
Copy link
Contributor

Choose a reason for hiding this comment

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

Terrible. What kind of test requires a twelve second timeout? Scipy is the worst.

It'll be really nice once jspi is shipping in browsers and we don't have to preload scipy's 100+ .so files. In the meantime I think we should get rid of kmeans. If a single test doesn't test anything and requires a twelve second timeout it needs to be gotten rid of. If it does test something can anyone tell me what it tests and why it can't be done in a scipy free way?

Copy link
Contributor

Choose a reason for hiding this comment

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

to be honest it feels like we test too much 3rd party in our integration when the goal should be "if 3rd party can be loaded, we're good" so I am actually curious myself why we take responsibility for all these foreign modules ... I understand the will to ensure stuff works, but having these as part of our testing pipeline seems a bit off to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

to clarify further ... I think these tests should run only before a release and not for every commit we make to this project ... that would save time (both ours + computation) and grant we can tackle foreign modules gotchas before a release, not daily ... imho that would be great to me, so that code-freeze on release is meant to tackle possible gotchas and fix these if needed.

Copy link
Member Author

Choose a reason for hiding this comment

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

@hoodmane for what it's worth, that's a 120 second timeout 🤢

I'm more than happy to go an entirely different direction with testing and removing the examples from the testing workflow, or testing them only prior to release, or what have you. I was mostly looking for a straightforward path to get this long-standing PR merged and the project moved up to Pyodide 0.23/Python 3.11.

How would we feel about merging this as-is and following up with a separate PR/discussion about improving the examples testing process?

Copy link
Contributor

Choose a reason for hiding this comment

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

that would work for me

Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than only before the release, I'd personally prefer to run them nightly or weekly or something like that. At least we are aware something broke sooner (and we don't have the overhead of testing examples before merging a PR)

In terms of testing too much 3rd party, I agree that we should test less and mainly focus on "if they load, we are good". There are some caveats though that I'd like for us to consider and add better support to specific 3rd party libraries. That's especially true for modules that are quite popular and don't just work with specifying them as dependencies. Take panel or bokeh as examples for instance, I'd like for us to make the user experience better and "auto-patch" everything they need to "just work" when you specify them as packages (but that is a totally different discussion and I think it probably belongs to PyScript core and rather to higher level API or something like this)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd personally prefer to run them nightly or weekly or something like that

that's a great idea too, as long as these are off our daily pipes around unrelated tasks

@JeffersGlass
Copy link
Member Author

Wow that's impressive - my little address comments commit caused every single examples test to fail... but only in CI. The same was happening in
e7d5190, so I've killed those tests and reverted to the previous implementation of the loadPackage logic was somehow better?? Bizarre.

@JeffersGlass
Copy link
Member Author

Oh it's not just me, it's now all of the examples tests that are failing, even in PR's with no change. See #1468. Will have to wait till whatever that is is resolved before testing this again.

@antocuni
Copy link
Contributor

@antocuni I refactored test_pyscript_src_not_found to remove the with pytest.raises(PageErrors) - if you look at the output of these successful CI tests vs these failed CI tests (between which nothing changed in the code), it seems there is a timing issue with using that syntax to wrap run_pyscript. It seems it's possible that the js error will not be raised prior to run_pyscript() finishing. I've added the check_js_errors parameters to run_pyscript itself and deferred looking for errors until later in the test. The relevant changes are in this commit.

@JeffersGlass I'm confused: the fact that you had to tweak the test in such a way looks like a code smell to me:

  • self.pyscript_run() should NOT return before the pyscript initialization is complete
  • pyscript should NOT print PyScript page fully initialized if there are <py-script> tags still pending
  • and if all <py-script> tags have been fully evaluated, then I expect to see the JS error before the end of self.pyscript_run()

This seems the relevant code:

await this.executeScripts(interpreter);
this.logStatus('Initializing web components...');
// lifecycle (8)
//Takes a runtime and a reference to the PyScriptApp (to access plugins)
createCustomElements(interpreter, this);
initHandlers(interpreter);
// NOTE: interpreter message is used by integration tests to know that
// pyscript initialization has complete. If you change it, you need to
// change it also in tests/integration/support.py
this.logStatus('Startup complete');
await this.plugins.afterStartup(interpreter);
logger.info('PyScript page fully initialized');

How is it possible that the page prints PyScript page fully initialized without having raised the 404 error?

@JeffersGlass
Copy link
Member Author

JeffersGlass commented May 23, 2023

I'm not sure how it's possible, but it seems to be true, at least if I'm reading the output correctly.

If js errors are to be caught, they should appear in the console before wait_for_console terminates:

# this is printed by interpreter.ts:Interpreter.initialize
elapsed_ms = self.wait_for_console(
"[pyscript/main] PyScript page fully initialized",
timeout=timeout,
check_js_errors=check_js_errors,
)
self.logger.log(
"wait_for_pyscript", f"Waited for {elapsed_ms/1000:.2f} s", color="yellow"
)
# We still don't know why this wait is necessary, but without it
# events aren't being triggered in the tests.
self.page.wait_for_timeout(100)

However, sometimes (but not always) the error does not appear in stddout until after it has completed and "Waited for X s" is printed. This is the relevant captured stdout from this test run. Note how console.js_error follows wait_for_pyscript.

...
[  8.51 console.info     ] [pyscript/main] Executing <py-script> tags...
[  8.51 request          ] 200 - fake_server - https://fake_server/foo.py
[  8.54 console.error    ] Failed to load resource: the server responded with a status of 404 (Not Found)
[  8.54 console.error    ] (PY0404): Fetching from URL foo.py failed with error 404 (Not Found). Are your filename and path correct?
[  8.56 console.info     ] [pyscript/main] Initializing web components...
[  8.57 console.debug    ] [py-script] Initializing py-* event handlers...
[  8.57 console.info     ] [pyscript/main] Startup complete
[  8.57 console.info     ] [py-splashscreen] Closing
[  8.57 console.info     ] [pyscript/main] PyScript page fully initialized
[  8.58 wait_for_pyscript] Waited for 8.24 s
[  8.59 console.js_error ] FetchError: (PY0404): Fetching from URL foo.py failed with error 404 (Not Found). Are your filename and path correct?
    at robustFetch (https://fake_server/build/pyscript.js:5080:17)
...

This test failed, because a JS error was expected by wait_for_console, but it did not appear until after wait_for_console terminted.

So I agree, something smells, and I think something about our understanding of the timing of the js_error output or the test mechanism is incomplete. I returned to manually testing for JS errors in a later place, when some other asserts have verified that the banner already exists, and therefore so should the message in the console.

@JeffersGlass
Copy link
Member Author

What I would propose is we merge this as-is, and then open a separate issue re-examining the issue around checking JS errors during run_python, since it's slightly orthogonal to this PR.

I know that's not ideal, but I'm trying (and failing) to prevent this PR from becoming a catch-all for discovered issues. (js_errors, how/when we run examples, panel/bokeh API changes...😬).

@antocuni
Copy link
Contributor

I'm fine with merging this PR as is but yes, the problems that it uncovered must be fixed.
If our test machinery is not bullet-proof, this means that it's just a matter of time before other tests will start failing for timing issues

@JeffersGlass
Copy link
Member Author

@antocuni Agreed! I am going to hit merge here, and open a new issue rehashing what the issue is.

@JeffersGlass JeffersGlass merged commit e1758ae into pyscript:main May 24, 2023
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tag: pyodide Related to issues with pyodide
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Upgrade to Pyodide 0.23
5 participants