File size: 4,500 Bytes
d1ceb73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""The client and server for a basic ping-pong style heartbeat.
"""

# -----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------

# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------

import errno
import socket
from pathlib import Path
from threading import Thread

import zmq
from jupyter_client.localinterfaces import localhost

# -----------------------------------------------------------------------------
# Code
# -----------------------------------------------------------------------------


class Heartbeat(Thread):
    """A simple ping-pong style heartbeat that runs in a thread."""

    def __init__(self, context, addr=None):
        """Initialize the heartbeat thread."""
        if addr is None:
            addr = ("tcp", localhost(), 0)
        Thread.__init__(self, name="Heartbeat")
        self.context = context
        self.transport, self.ip, self.port = addr
        self.original_port = self.port
        if self.original_port == 0:
            self.pick_port()
        self.addr = (self.ip, self.port)
        self.daemon = True
        self.pydev_do_not_trace = True
        self.is_pydev_daemon_thread = True
        self.name = "Heartbeat"

    def pick_port(self):
        """Pick a port for the heartbeat."""
        if self.transport == "tcp":
            s = socket.socket()
            # '*' means all interfaces to 0MQ, which is '' to socket.socket
            s.bind(("" if self.ip == "*" else self.ip, 0))
            self.port = s.getsockname()[1]
            s.close()
        elif self.transport == "ipc":
            self.port = 1
            while Path(f"{self.ip}-{self.port}").exists():
                self.port = self.port + 1
        else:
            raise ValueError("Unrecognized zmq transport: %s" % self.transport)
        return self.port

    def _try_bind_socket(self):
        c = ":" if self.transport == "tcp" else "-"
        return self.socket.bind(f"{self.transport}://{self.ip}" + c + str(self.port))

    def _bind_socket(self):
        try:
            win_in_use = errno.WSAEADDRINUSE  # type:ignore[attr-defined]
        except AttributeError:
            win_in_use = None

        # Try up to 100 times to bind a port when in conflict to avoid
        # infinite attempts in bad setups
        max_attempts = 1 if self.original_port else 100
        for attempt in range(max_attempts):
            try:
                self._try_bind_socket()
            except zmq.ZMQError as ze:
                if attempt == max_attempts - 1:
                    raise
                # Raise if we have any error not related to socket binding
                if ze.errno != errno.EADDRINUSE and ze.errno != win_in_use:
                    raise
                # Raise if we have any error not related to socket binding
                if self.original_port == 0:
                    self.pick_port()
                else:
                    raise
            else:
                return

    def run(self):
        """Run the heartbeat thread."""
        self.name = "Heartbeat"
        self.socket = self.context.socket(zmq.ROUTER)
        self.socket.linger = 1000
        try:
            self._bind_socket()
        except Exception:
            self.socket.close()
            raise

        while True:
            try:
                zmq.device(zmq.QUEUE, self.socket, self.socket)
            except zmq.ZMQError as e:
                if e.errno == errno.EINTR:
                    # signal interrupt, resume heartbeat
                    continue
                if e.errno == zmq.ETERM:
                    # context terminated, close socket and exit
                    try:
                        self.socket.close()
                    except zmq.ZMQError:
                        # suppress further errors during cleanup
                        # this shouldn't happen, though
                        pass
                    break
                if e.errno == zmq.ENOTSOCK:
                    # socket closed elsewhere, exit
                    break
                raise
            else:
                break