Skip to content

Exception 'signal only works in main thread' when Connection.run command with pty=True #2204

Description

@francesco-giordano

Describe the bug
When running a command with Connection.run and argument pty=True in a thread we get that error. fabric version 2.7.0

To Reproduce
Steps to reproduce the behaviour (please attach a minimal example):
Running the code below on fabric 2.6.0 everything work fine, while if it is run with 2.7.0 it fails due to this line

signal.signal(signal.SIGWINCH, self.handle_window_change)

from fabric import Connection
import threading
def function(connection):
    command="uptime"
    connection.run(command, warn=True, pty=True, hide=False, timeout=None)

connection_kwargs = {
    "host":  <MY_IP>,
    "user": <MY_USER>,
    "forward_agent": False,
    "connect_kwargs": {"key_filename": "<PATH TO MY KEY>"},
}
connection = Connection(**connection_kwargs)
command="uptime"

connection.run(command, warn=True, pty=True, hide=False, timeout=None)
t1=threading.Thread(target=function, args=(connection,))
t1.start()
t1.join()

Expected behaviour
fabric works within a thread

Environment
Bug seen in python 3.7.2 and 3.10.1

  • What version of the Python interpreter are you using? Are you using an
    alternative interpreter such as PyPy?
  • What operating system are you using both client & server-side? Ubuntu, centos and amazon linux as server and mac as client
  • Are you using OpenSSH server or something else? OpenSSH
  • Which version or versions of the software are you using?
    • Have you already tried the latest release? yes
    • Have you, or can you, try some older releases to pin down where the bug
      appeared? 2.6.0 work fine
  • How can the developers recreate the bug on their end? If possible, include a
    copy of your code, the command you used to invoke it, and the full output of
    your run (if applicable.)
 13:28:46 up 3 days, 20:17,  1 user,  load average: 0.00, 0.00, 0.00
Exception in thread Thread-5 (function):
Traceback (most recent call last):
  File "/Users/giordafr/.pyenv/versions/3.10.1/lib/python3.10/threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "/Users/giordafr/.pyenv/versions/3.10.1/lib/python3.10/threading.py", line 946, in run
    self._target(*self._args, **self._kwargs)
  File "/tmp/test.py", line 5, in function
    connection.run(command, warn=True, pty=True, hide=False, timeout=None)
  File "<decorator-gen-3>", line 2, in run
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/fabric/connection.py", line 30, in opens
    return method(self, *args, **kwargs)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/fabric/connection.py", line 725, in run
    return self._run(self._remote_runner(), command, **kwargs)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/invoke/context.py", line 102, in _run
    return runner.run(command, **kwargs)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/fabric/runners.py", line 72, in run
    return super(Remote, self).run(command, **kwargs)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/invoke/runners.py", line 379, in run
    return self._run_body(command, **kwargs)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/invoke/runners.py", line 430, in _run_body
    self.start(command, self.opts["shell"], self.env)
  File "/Users/giordafr/.pyenv/versions/parallel-cluster-tests/lib/python3.10/site-packages/fabric/runners.py", line 46, in start
    signal.signal(signal.SIGWINCH, self.handle_window_change)
  File "/Users/giordafr/.pyenv/versions/3.10.1/lib/python3.10/signal.py", line 47, in signal
    handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
ValueError: signal only works in main thread of the main interpreter

Activity

  1. added a commit that references this issue on Mar 28, 2022
  2. Shooshp commented on Apr 14, 2022

    @Shooshp

    Quite annoying bug, have to rollback from 2.7.0 on 2.6.0.
    Environment - python:3.9-slim docker container.

  3. edsharp commented on Apr 28, 2022

    @edsharp

    We are seeing a similar signal only works in main thread regression in behaviour in 2.7 when invoking .run with

                                "warn": True,
                                "echo": True,
                                "pty": True,
    

    Fabric 2.6.0 worked without issue.

    In both cases we were using python 3.8.

  4. demartinofra commented on Nov 8, 2022

    @demartinofra

    Hello, is there any update on a potential fix for this issue? Thank you!

  5. Crux-One commented on Nov 22, 2022

    @Crux-One

    v2.7 likely contains bugs.
    Here is my Pipfile. It works.

    [packages]
    fabric = "==2.6"
    
    [requires]
    python_version = "3.9.9"
  6. juchen-dev commented on Apr 4, 2023

    @juchen-dev

    Same issue on python 3.9

  7. gmarciani commented on Jun 5, 2023

    @gmarciani

    Hello, is there any update on the patch?

  8. juchen-dev commented on Jun 5, 2023

    @juchen-dev

    I use fabric as a library, and was able to get around this by monkey patching the problematic part:

    from invoke.terminals import pty_size
    from fabric.main import Argument, Config, Executor, Fab
    
    def patched_start(self, command, shell, env, timeout=None):
        self.channel = self.context.create_session()
        if self.using_pty:
            cols, rows = pty_size()
            self.channel.get_pty(width=cols, height=rows)
            # NOTE: Everything else is the same except for these two commented lines
            # if hasattr(signal, "SIGWINCH"):
            #     signal.signal(signal.SIGWINCH, self.handle_window_change)
        if env:
            if self.inline_env:
                parameters = " ".join(["{}={}".format(k, v) for k, v in sorted(env.items())])
                command = "export {} && {}".format(parameters, command)
            else:
                self.channel.update_environment(env)
        self.send_start_message(command)
    
    class MyProgram(Fab):
        def update_config(self):
            # This lets us patch Remote.start with the function above
            self.config.runners.remote.start = patched_start
            super().update_config()
    
    program = MyProgram(
        name="Fabric",
        version="1.0.0",
        executor_class=Executor,
        config_class=Config,
    )
    
    sys.exit(program.run())
  9. bitprophet commented on Aug 31, 2023

    @bitprophet
    Member

    Ran into this on CircleCI while fixing a related issue - it looks like the simple check of "are we the main thread?" is sufficient to avoid the problem.

    That fix will be released imminently in Fabric 3.2.2.

  10. added 4 commits that reference this issue on Aug 31, 2023
    2192f2e
    b2a517f
    67e9029
    21b24d2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions