-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathprocesses.py
More file actions
204 lines (156 loc) · 5.64 KB
/
processes.py
File metadata and controls
204 lines (156 loc) · 5.64 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
"""
botogram.runner.processes
Definition of all of the running processes
Copyright (c) 2015 Pietro Albini <pietro@pietroalbini.io>
Released under the MIT license
"""
import multiprocessing
import os
import traceback
import queue
import time
import signal
import logbook
from . import jobs
from . import shared
from . import ipc
from .. import objects
from .. import api
from .. import updates as updates_module
class BaseProcess(multiprocessing.Process):
"""Base class for all of the processes"""
def __init__(self, ipc_info, *args):
self.stop = False
self.logger = logbook.Logger("botogram subprocess")
self.ipc = None
if ipc_info is not None:
self.ipc = ipc.IPCClient(*ipc_info)
super(BaseProcess, self).__init__()
self.setup(*args)
def setup(self, *args):
"""Setup the class"""
pass
def run(self):
"""Run the process"""
for one in signal.SIGINT, signal.SIGTERM:
signal.signal(one, _ignore_signal)
self.logger.debug("%s process is ready! (pid: %s)" % (self.name,
os.getpid()))
while not self.stop:
try:
self.loop()
except ipc.IPCServerCrashedError:
self.logger.error("The IPC server just crashed. Please kill "
"the runner.")
self.on_stop()
except (KeyboardInterrupt, InterruptedError):
self.on_stop()
except:
traceback.print_exc()
self.after_stop()
# Be sure to close the IPC connection
if self.ipc:
self.ipc.close()
self.logger.debug("%s process with pid %s just stopped" % (self.name,
os.getpid()))
def loop(self):
"""One single loop"""
pass
def after_stop(self):
"""After the process stops"""
pass
def on_stop(self):
"""When the process is stopping"""
self.stop = True
class IPCProcess(BaseProcess):
"""This process will handle IPC requests"""
name = "IPC"
def setup(self, ipc):
self.ipc_server = ipc
# Setup the jobs commands
self.jobs_commands = jobs.JobsCommands()
ipc.register_command("jobs.bulk_put", self.jobs_commands.bulk_put)
ipc.register_command("jobs.get", self.jobs_commands.get)
ipc.register_command("jobs.shutdown", self.jobs_commands.shutdown)
# Setup the shared commands
self.shared_backend = shared.SharedStateBackend()
shared_commands = [
"object_list", "object_exists", "object_type", "object_create",
"dict_length", "dict_item_get", "dict_item_set",
"dict_item_delete", "dict_contains", "dict_keys", "dict_values",
"dict_items", "dict_clear", "dict_pop", "lock_acquire",
"lock_release", "lock_status", "data_import", "data_export",
]
for cmd in shared_commands:
command = getattr(self.shared_backend, cmd)
ipc.register_command("shared.%s" % cmd, command)
def loop(self):
self.ipc_server.run()
# This will stop running the loop
super(IPCProcess, self).on_stop()
def on_stop(self):
super(IPCProcess, self).on_stop()
self.ipc_server.stop = True
class WorkerProcess(BaseProcess):
"""This process will execute all the updates it receives"""
name = "Worker"
def setup(self, bots):
self.bots = bots
def loop(self):
# Request a new job
try:
job = self.ipc.command("jobs.get", None)
except InterruptedError:
# This return acts as a continue
return
# If the job is None, stop the worker
if job == "__stop__":
self.stop = True
return
# Run the wanted job
job.process(self.bots)
class UpdaterProcess(BaseProcess):
"""This process will fetch the updates"""
name = "Updater"
def setup(self, bot, commands):
self.bot = bot
self.bot_id = bot._bot_id
self.commands = commands
self.fetcher = updates_module.UpdatesFetcher(bot)
def loop(self):
# This allows to control the process
try:
command = self.commands.get(False)
# The None command will stop the process
if command == "stop":
self.stop = True
return
except queue.Empty:
pass
try:
updates, backlog = self.fetcher.fetch()
except api.APIError as e:
self.logger.error("An error occured while fetching updates!")
self.logger.debug("Exception type: %s" % e.__class__.__name__)
self.logger.debug("Exception content: %s" % str(e))
return
if backlog:
if len(backlog) == 1:
self.logger.debug("Skipped update #%s because it's coming "
"from the backlog" % backlog[0].update_id)
else:
self.logger.debug("Skipped updates #%s to #%s because they're "
"coming from the backlog" % (
backlog[0].update_id,
backlog[-1].update_id
))
if updates:
result = []
for update in updates:
update.set_api(None)
result.append(jobs.Job(self.bot_id, jobs.process_update, {
"update": update,
}))
self.ipc.command("jobs.bulk_put", result)
def _ignore_signal(*__):
pass