quarto-dev / quarto-dev/quarto

Markdown is not always converted to python in multiprocessing/threading from the Quarto VS Code extension

Open
#401 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
645
Forks
62
Avg merge
17h 42m
Merged PRs (30d)
13

Description

This is a cross-post of https://github.com/quarto-dev/quarto-cli/issues/9134 based on @cscheid's recommendation in https://github.com/quarto-dev/quarto-cli/issues/9134#issuecomment-2008161485. It affects both the CLI and the VS Code extension. The error that appears in VS Code is shown at the top level while the error that appears in the CLI is folded below it for reference.

Bug description

I recognize this is not an ideal design pattern, but calling a third-party library that uses multiprocessing and threading as shown in the minimal example below, results in an error suggesting the markdown may be passed to the process without being converted to python. The source code in the example works as expected in a python interpreter or jupyter kernel.

Steps to reproduce
near-minimal example

---
title: "Markdown is not always converted to python in multiprocessing/threading"
format: html
execute:
  enabled: true
jupyter:
  kernelspec:
    display_name: "Python 3"
    language: python
    name: python3
---

## Minimal example

This notebook demonstrates a potential issue with rendering notebooks using `multiprocessing.Manager().Queue()` and `threading.Thread`.

```{python}
from multiprocessing import Manager
from threading import Thread
try:
    from tqdm import tqdm
except ImportError:
    tqdm = None

def update(progress_bar, queue, total):
    """Update progress bar based on values from the queue."""
    for _ in range(total):
        queue.get()
        if progress_bar is not None:
            progress_bar.update(1)

def simulate_issue():
    range_length = 10
    unit = "items"
    
    progress_bar = None if tqdm is None else tqdm(total=range_length, unit=unit)
    queue = Manager().Queue()
    thread = Thread(target=update, args=(progress_bar, queue, range_length))
    thread.start()
    
    for _ in range(range_length):
        queue.put('done')
    
    thread.join()
    if progress_bar is not None:
        progress_bar.close()
```

executing `simulate_issue()` proceeds without error in a jupyter notebook or ipython terminal

```{python}
simulate_issue()
```

but leads to

```pytb
  0%|          | 0/10 [00:00<?, ?items/s]Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 125, in _main
    prepare(preparation_data)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 236, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 287, in _fixup_main_from_path
    main_content = runpy.run_path(main_path,
  File "~/.pyenv/versions/3.10.13/lib/python3.10/runpy.py", line 288, in run_path
    code, fname = _get_code_from_file(run_name, path_name)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/runpy.py", line 257, in _get_code_from_file
    code = compile(f.read(), fname, 'exec')
  File "~/template.qmd", line 1
    ---
       ^
SyntaxError: invalid syntax
```

when executed from the Quarto VS Code extension.

Expected behavior

The notebook should render without error.

Actual behavior

The following error is surfaced in an interactive session from the VS Code extension

  0%|          | 0/10 [00:00<?, ?items/s]Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 125, in _main
    prepare(preparation_data)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 236, in prepare
    _fixup_main_from_path(data['init_main_from_path'])
  File "~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py", line 287, in _fixup_main_from_path
    main_content = runpy.run_path(main_path,
  File "~/.pyenv/versions/3.10.13/lib/python3.10/runpy.py", line 288, in run_path
    code, fname = _get_code_from_file(run_name, path_name)
  File "~/.pyenv/versions/3.10.13/lib/python3.10/runpy.py", line 257, in _get_code_from_file
    code = compile(f.read(), fname, 'exec')
  File "~/template.qmd", line 1
    ---
       ^
SyntaxError: invalid syntax

while

terminal traceback yields

❯ quarto render template.qmd --debug

Starting python3 kernel...Done

Executing 'template.ipynb'
  Cell 1/2: ''...Done
  Cell 2/2: ''...ERROR: 

An error occurred while executing the following cell:
------------------
simulate_issue()
------------------

----- stderr -----
  0%|          | 0/10 [00:00<?, ?items/s]
------------------

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[2], line 1
----> 1 simulate_issue()

Cell In[1], line 20, in simulate_issue()
     17 unit = "items"
     19 progress_bar = None if tqdm is None else tqdm(total=range_length, unit=unit)
---> 20 queue = Manager().Queue()
     21 thread = Thread(target=update, args=(progress_bar, queue, range_length))
     22 thread.start()

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/context.py:57, in BaseContext.Manager(self)
     55 from .managers import SyncManager
     56 m = SyncManager(ctx=self.get_context())
---> 57 m.start()
     58 return m

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/managers.py:562, in BaseManager.start(self, initializer, initargs)
    560 ident = ':'.join(str(i) for i in self._process._identity)
    561 self._process.name = type(self).__name__  + '-' + ident
--> 562 self._process.start()
    564 # get address of server
    565 writer.close()

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/process.py:121, in BaseProcess.start(self)
    118 assert not _current_process._config.get('daemon'), \
    119        'daemonic processes are not allowed to have children'
    120 _cleanup()
--> 121 self._popen = self._Popen(self)
    122 self._sentinel = self._popen.sentinel
    123 # Avoid a refcycle if the target function holds an indirect
    124 # reference to the process object (see bpo-30775)

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/context.py:288, in SpawnProcess._Popen(process_obj)
    285 @staticmethod
    286 def _Popen(process_obj):
    287     from .popen_spawn_posix import Popen
--> 288     return Popen(process_obj)

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/popen_spawn_posix.py:32, in Popen.__init__(self, process_obj)
     30 def __init__(self, process_obj):
     31     self._fds = []
---> 32     super().__init__(process_obj)

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/popen_fork.py:19, in Popen.__init__(self, process_obj)
     17 self.returncode = None
     18 self.finalizer = None
---> 19 self._launch(process_obj)

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/popen_spawn_posix.py:42, in Popen._launch(self, process_obj)
     40 tracker_fd = resource_tracker.getfd()
     41 self._fds.append(tracker_fd)
---> 42 prep_data = spawn.get_preparation_data(process_obj._name)
     43 fp = io.BytesIO()
     44 set_spawning_popen(self)

File ~/.pyenv/versions/3.10.13/lib/python3.10/multiprocessing/spawn.py:183, in get_preparation_data(name)
    180 # Figure out whether to initialise main in the subprocess as a module
    181 # or through direct execution (or to leave it alone entirely)
    182 main_module = sys.modules['__main__']
--> 183 main_mod_name = getattr(main_module.__spec__, "name", None)
    184 if main_mod_name is not None:
    185     d['init_main_from_name'] = main_mod_name

AttributeError: module '__main__' has no attribute '__spec__'

Your environment
environment

❯ code --version                         
1.87.2
863d2581ecda6849923a2118d93a088b0745d9d6
arm64

❯ uname -a                               
Darwin 22.6.0 Darwin Kernel Version 22.6.0: Fri Sep 15 13:41:28 PDT 2023; root:xnu-8796.141.3.700.8~1/RELEASE_ARM64_T6000 arm64

❯ sw_vers -productVersion          
13.6

Quarto check output
quarto check

❯ quarto check                                                                              
Quarto 1.4.551
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.1.11: OK
      Dart Sass version 1.69.5: OK
      Deno version 1.37.2: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 1.4.551
      Path: /Applications/quarto/bin

[✓] Checking tools....................OK
      TinyTeX: (not installed)
      Chromium: (not installed)

[✓] Checking LaTeX....................OK
      Using: Installation From Path
      Path: /Library/TeX/texbin
      Version: 2023

[✓] Checking basic markdown render....OK

[✓] Checking Python 3 installation....OK
      Version: 3.10.13
      Path: /xxxx-py3.10/bin/python3
      Jupyter: 5.3.0
      Kernels: ir, julia-1.9, bash, maxima, python3

[✓] Checking Jupyter engine render....OK

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with template.qmd using quarto render template.qmd --debug, then compare the CLI and VS Code extension execution paths around the generated template.ipynb and Python kernel. Done means the minimal multiprocessing/threading example renders without the Markdown YAML front matter being passed to Python, in both interfaces.

Written by the indexing model from the issue text.

Assessment

Tech stack
jupyter, python, vscode
Domain
cli, developer-experience, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.