• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hivesolutions / flask-quorum / #620457871

12 Apr 2024 02:06PM UTC coverage: 66.296%. First build
#620457871

travis-ci

6202 of 9355 relevant lines covered (66.3%)

1.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.92
/src/quorum/daemon.py
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
3

4
# Hive Flask Quorum
5
# Copyright (c) 2008-2025 Hive Solutions Lda.
6
#
7
# This file is part of Hive Flask Quorum.
8
#
9
# Hive Flask Quorum is free software: you can redistribute it and/or modify
10
# it under the terms of the Apache License as published by the Apache
11
# Foundation, either version 2.0 of the License, or (at your option) any
12
# later version.
13
#
14
# Hive Flask Quorum is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# Apache License for more details.
18
#
19
# You should have received a copy of the Apache License along with
20
# Hive Flask Quorum. If not, see <http://www.apache.org/licenses/>.
21

22
__author__ = "João Magalhães <joamag@hive.pt>"
2✔
23
""" The author(s) of the module """
24

25
__copyright__ = "Copyright (c) 2008-2025 Hive Solutions Lda."
2✔
26
""" The copyright for the module """
27

28
__license__ = "Apache License, Version 2.0"
2✔
29
""" The license for the module """
30

31
import os
2✔
32
import sys
2✔
33
import time
2✔
34
import atexit
2✔
35
import signal
2✔
36

37

38
class Daemon(object):
2✔
39
    """
40
    A generic daemon class that provides the general
41
    daemon capabilities. In order to inherit the daemon
42
    capabilities override the run method.
43
    """
44

45
    pidfile = None
2✔
46
    """ The path to the file that will hold the
47
    pid of the created daemon """
48

49
    stdin = None
2✔
50
    """ The file path to the file to be used
51
    as the standard input of the created process """
52

53
    stdout = None
2✔
54
    """ The file path to the file to be used
55
    as the standard output of the created process """
56

57
    stderr = None
2✔
58
    """ The file path to the file to be used
59
    as the standard error of the created process """
60

61
    def __init__(
2✔
62
        self, pid_file, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null"
63
    ):
64
        """
65
        Constructor of the class.
66

67
        :type pidfile: String
68
        :param pidfile: The path to the pid file.
69
        :type stdin: String
70
        :param stdin: The file path to the file to be used
71
        as the standard input of the created process.
72
        :type stdout: String
73
        :param stdout: The file path to the file to be used
74
        as the standard output of the created process.
75
        :type stderr: String
76
        :param stderr: The file path to the file to be used
77
        as the standard error of the created process.
78
        """
79

80
        self.pidfile = pid_file
×
81
        self.stdin = stdin
×
82
        self.stdout = stdout
×
83
        self.stderr = stderr
×
84

85
    def daemonize(self, register=True):
2✔
86
        """
87
        Do the UNIX double-fork magic, check Stevens "Advanced
88
        Programming in the UNIX Environment" for details (ISBN
89
        0201563177).
90

91
        This is considered the main method for the execution
92
        of the daemon strategy.
93

94
        :type register: bool
95
        :param register: If a cleanup function should be register for
96
        the at exit operation.
97
        :see: http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
98
        """
99

100
        try:
×
101
            pid = os.fork()  # @UndefinedVariable
×
102
            if pid > 0:
×
103
                sys.exit(0)
×
104
        except OSError as error:
×
105
            sys.stderr.write(
×
106
                "first fork failed: %d (%s)\n" % (error.errno, error.strerror)
107
            )
108
            sys.exit(1)
×
109

110
        # decouples the current process from parent environment
111
        # should create a new group of execution
112
        os.chdir("/")
×
113
        os.setsid()  # @UndefinedVariable
×
114
        os.umask(0)
×
115

116
        try:
×
117
            # runs the second for and then exits from
118
            # the "second" parent process
119
            pid = os.fork()  # @UndefinedVariable
×
120
            if pid > 0:
×
121
                sys.exit(0)
×
122
        except OSError as error:
×
123
            sys.stderr.write(
×
124
                "second fork failed: %d (%s)\n" % (error.errno, error.strerror)
125
            )
126
            sys.exit(1)
×
127

128
        # redirect standard file descriptors
129
        sys.stdout.flush()
×
130
        sys.stderr.flush()
×
131
        si = open(self.stdin, "rb")
×
132
        so = open(self.stdout, "ab+")
×
133
        se = open(self.stderr, "ab+", 0)
×
134
        os.dup2(si.fileno(), sys.stdin.fileno())
×
135
        os.dup2(so.fileno(), sys.stdout.fileno())
×
136
        os.dup2(se.fileno(), sys.stderr.fileno())
×
137

138
        # write pidfile, updating the data in it
139
        # this should mark the process as running
140
        register and atexit.register(self.cleanup)
×
141
        register and signal.signal(signal.SIGTERM, self.cleanup_s)
×
142
        pid = str(os.getpid())
×
143
        open(self.pidfile, "wb+").write("%s\n" % pid)
×
144

145
    def start(self, register=True):
2✔
146
        try:
×
147
            # checks for a pidfile to check if the daemon
148
            # already runs, in such case retrieves the pid
149
            # of the executing daemon
150
            pid_file = open(self.pidfile, "rb")
×
151
            pid_contents = pid_file.read().strip()
×
152
            pid = int(pid_contents)
×
153
            pid_file.close()
×
154
        except IOError:
×
155
            pid = None
×
156

157
        # in case the pid value is loaded, prints an error
158
        # message to the standard error and exists the current
159
        # process (avoids duplicated running)
160
        if pid:
×
161
            message = "pidfile %s already exists, daemon already running ?\n"
×
162
            sys.stderr.write(message % self.pidfile)
×
163
            sys.exit(1)
×
164

165
        # daemonizes the current process and then starts
166
        # the daemon structures (runs the process)
167
        self.daemonize(register=register)
×
168
        self.run()
×
169

170
    def stop(self):
2✔
171
        try:
×
172
            # checks for a pidfile to check if the daemon
173
            # already runs, in such case retrieves the pid
174
            # of the executing daemon
175
            pid_file = open(self.pidfile, "rb")
×
176
            pid_contents = pid_file.read().strip()
×
177
            pid = int(pid_contents)
×
178
            pid_file.close()
×
179
        except IOError:
×
180
            pid = None
×
181

182
        if not pid:
×
183
            message = "pidfile %s does not exist, daemon not running ?\n"
×
184
            sys.stderr.write(message % self.pidfile)
×
185
            return
×
186

187
        try:
×
188
            while True:
×
189
                os.kill(pid, signal.SIGTERM)  # @UndefinedVariable
×
190
                time.sleep(0.1)
×
191
        except OSError as error:
×
192
            error = str(error)
×
193
            if error.find("No such process") > 0:
×
194
                pid_exists = os.path.exists(self.pidfile)
×
195
                pid_exists and os.remove(self.pidfile)
×
196
            else:
197
                sys.exit(1)
×
198

199
    def restart(self):
2✔
200
        """
201
        Restarts the daemon process stopping it and
202
        then starting it "again".
203
        """
204

205
        self.stop()
×
206
        self.start()
×
207

208
    def cleanup(self):
2✔
209
        """
210
        Performs a cleanup operation in the current daemon
211
        releasing all the structures locked by it.
212
        """
213

214
        self.delete_pid()
×
215

216
    def cleanup_s(self, signum, frame):
2✔
217
        """
218
        Cleanup handler for the signal handler, this handler
219
        takes extra arguments required by the signal handler
220
        caller.
221

222
        :type signum: int
223
        :param signum: The identifier of the signal that has
224
        just been raised.
225
        :type frame: Object
226
        :param frame: The object containing the current program
227
        frame at the time of the signal raise.
228
        """
229

230
        self.cleanup()
×
231

232
    def delete_pid(self):
2✔
233
        """
234
        Removes the current pid file in case it exists in the
235
        current file system.
236

237
        No error will be raised in case no pid file exists.
238
        """
239

240
        pid_exists = os.path.exists(self.pidfile)
×
241
        pid_exists and os.remove(self.pidfile)
×
242

243
    def run(self):
2✔
244
        """
245
        You should override this method when you subclass
246
        daemon. It will be called after the process has been
247
        daemonized by start or restart methods.
248
        """
249

250
        pass
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc