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

hivesolutions / flask-quorum / 1494

pending completion
1494

Pull #2

travis-ci-com

web-flow
Merge f1aa62957 into d47604bd0
Pull Request #2: Fixes removal of `_app_ctx_stack`

16 of 16 new or added lines in 1 file covered. (100.0%)

6002 of 8464 relevant lines covered (70.91%)

5.53 hits per line

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

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

4
# Hive Flask Quorum
5
# Copyright (c) 2008-2022 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>"
8✔
23
""" The author(s) of the module """
24

25
__version__ = "1.0.0"
8✔
26
""" The version of the module """
27

28
__revision__ = "$LastChangedRevision$"
8✔
29
""" The revision number of the module """
30

31
__date__ = "$LastChangedDate$"
8✔
32
""" The last change date of the module """
33

34
__copyright__ = "Copyright (c) 2008-2022 Hive Solutions Lda."
8✔
35
""" The copyright for the module """
36

37
__license__ = "Apache License, Version 2.0"
8✔
38
""" The license for the module """
39

40
import os
8✔
41
import sys
8✔
42
import time
8✔
43
import atexit
8✔
44
import signal
8✔
45

46
class Daemon(object):
8✔
47
    """
48
    A generic daemon class that provides the general
49
    daemon capabilities. In order to inherit the daemon
50
    capabilities override the run method.
51
    """
52

53
    pidfile = None
8✔
54
    """ The path to the file that will hold the
55
    pid of the created daemon """
56

57
    stdin = None
8✔
58
    """ The file path to the file to be used
59
    as the standard input of the created process """
60

61
    stdout = None
8✔
62
    """ The file path to the file to be used
63
    as the standard output of the created process """
64

65
    stderr = None
8✔
66
    """ The file path to the file to be used
67
    as the standard error of the created process """
68

69
    def __init__(self, pid_file, stdin = "/dev/null", stdout = "/dev/null", stderr = "/dev/null"):
8✔
70
        """
71
        Constructor of the class.
72

73
        :type pidfile: String
74
        :param pidfile: The path to the pid file.
75
        :type stdin: String
76
        :param stdin: The file path to the file to be used
77
        as the standard input of the created process.
78
        :type stdout: String
79
        :param stdout: The file path to the file to be used
80
        as the standard output of the created process.
81
        :type stderr: String
82
        :param stderr: The file path to the file to be used
83
        as the standard error of the created process.
84
        """
85

86
        self.pidfile = pid_file
×
87
        self.stdin = stdin
×
88
        self.stdout = stdout
×
89
        self.stderr = stderr
×
90

91
    def daemonize(self, register = True):
8✔
92
        """
93
        Do the UNIX double-fork magic, check Stevens "Advanced
94
        Programming in the UNIX Environment" for details (ISBN
95
        0201563177).
96

97
        This is considered the main method for the execution
98
        of the daemon strategy.
99

100
        :type register: bool
101
        :param register: If a cleanup function should be register for
102
        the at exit operation.
103
        :see: http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
104
        """
105

106
        try:
×
107
            pid = os.fork() #@UndefinedVariable
×
108
            if pid > 0: sys.exit(0)
×
109
        except OSError as error:
×
110
            sys.stderr.write(
×
111
                "first fork failed: %d (%s)\n" % (error.errno, error.strerror)
112
            )
113
            sys.exit(1)
×
114

115
        # decouples the current process from parent environment
116
        # should create a new group of execution
117
        os.chdir("/")
×
118
        os.setsid() #@UndefinedVariable
×
119
        os.umask(0)
×
120

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

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

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

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

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

169
        # daemonizes the current process and then starts
170
        # the daemon structures (runs the process)
171
        self.daemonize(register = register)
×
172
        self.run()
×
173

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

186
        if not pid:
×
187
            message = "pidfile %s does not exist, daemon not running ?\n"
×
188
            sys.stderr.write(message % self.pidfile)
×
189
            return
×
190

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

203
    def restart(self):
8✔
204
        """
205
        Restarts the daemon process stopping it and
206
        then starting it "again".
207
        """
208

209
        self.stop()
×
210
        self.start()
×
211

212
    def cleanup(self):
8✔
213
        """
214
        Performs a cleanup operation in the current daemon
215
        releasing all the structures locked by it.
216
        """
217

218
        self.delete_pid()
×
219

220
    def cleanup_s(self, signum, frame):
8✔
221
        """
222
        Cleanup handler for the signal handler, this handler
223
        takes extra arguments required by the signal handler
224
        caller.
225

226
        :type signum: int
227
        :param signum: The identifier of the signal that has
228
        just been raised.
229
        :type frame: Object
230
        :param frame: The object containing the current program
231
        frame at the time of the signal raise.
232
        """
233

234
        self.cleanup()
×
235

236
    def delete_pid(self):
8✔
237
        """
238
        Removes the current pid file in case it exists in the
239
        current file system.
240

241
        No error will be raised in case no pid file exists.
242
        """
243

244
        pid_exists = os.path.exists(self.pidfile)
×
245
        pid_exists and os.remove(self.pidfile)
×
246

247
    def run(self):
8✔
248
        """
249
        You should override this method when you subclass
250
        daemon. It will be called after the process has been
251
        daemonized by start or restart methods.
252
        """
253

254
        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