-
Notifications
You must be signed in to change notification settings - Fork 115
Expand file tree
/
Copy pathcontext.py
More file actions
350 lines (243 loc) · 9.35 KB
/
Copy pathcontext.py
File metadata and controls
350 lines (243 loc) · 9.35 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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
from future import standard_library
standard_library.install_aliases()
from future.builtins import next, map
from past.builtins import basestring
from .logger import Logger
import gevent
import gevent.pool
import urllib.parse
import time
import pymongo
import traceback
from .utils import LazyObject, load_class_by_path
from itertools import count as itertools_count
from .config import get_config
# This should be MRQ's only Python object shared by all the jobs in the same process
_GLOBAL_CONTEXT = {
# Contains all the running greenlets for this worker. greenletid => Job object
"greenlets": {},
# pointer to the current worker
"worker": None,
# pointer to the current config
"config": {}
}
# Global log object, usable from all jobs
log = Logger(None, job="current")
def setup_context(**kwargs):
""" Setup MRQ's environment.
Note: gevent should probably be initialized too if you want to use concurrency.
"""
set_current_config(get_config(**kwargs))
def set_current_job(job):
current = gevent.getcurrent()
current.__dict__["_trace_time"] = 0
current.__dict__["_trace_switches"] = 0
if job is None:
if id(current) in _GLOBAL_CONTEXT["greenlets"]:
del _GLOBAL_CONTEXT["greenlets"][id(current)]
else:
_GLOBAL_CONTEXT["greenlets"][id(current)] = job
def get_current_job(greenlet_id=None):
if greenlet_id is None:
greenlet_id = id(gevent.getcurrent())
return _GLOBAL_CONTEXT["greenlets"].get(greenlet_id)
def set_current_worker(worker):
_GLOBAL_CONTEXT["worker"] = worker
def get_current_worker():
return _GLOBAL_CONTEXT["worker"]
def set_current_config(config):
_GLOBAL_CONTEXT["config"] = config
log.quiet = config["quiet"]
if config["add_network_latency"] != "0" and config["add_network_latency"]:
from mrq.monkey import patch_network_latency
patch_network_latency(config["add_network_latency"])
if config["print_mongodb"] or config["trace_io"]:
from mrq.monkey import patch_pymongo
patch_pymongo(config)
if config["trace_io"]:
from mrq.monkey import patch_io_all
patch_io_all(config)
if config["mongodb_logs"] == "0":
log.handler.collection = False
def get_current_config():
return _GLOBAL_CONTEXT["config"]
def retry_current_job(delay=None, max_retries=None, queue=None):
current_job = get_current_job()
if current_job:
current_job.retry(delay=delay, max_retries=max_retries, queue=queue)
def abort_current_job():
current_job = get_current_job()
if current_job:
current_job.abort()
def _connections_factory(attr):
config = get_current_config()
# Connection strings may be stored directly in config
config_obj = config.get(attr)
def versiontuple(v):
return tuple(map(int, (v.split("."))))
if attr.startswith("redis"):
if isinstance(config_obj, basestring):
import redis as pyredis
urllib.parse.uses_netloc.append('redis')
redis_url = urllib.parse.urlparse(config_obj)
log.info("%s: Connecting to Redis at %s..." %
(attr, redis_url.hostname))
redis_pool = pyredis.BlockingConnectionPool(
host=redis_url.hostname,
port=redis_url.port,
db=int((redis_url.path or "").replace("/", "") or "0"),
password=redis_url.password,
max_connections=int(config.get("redis_max_connections")),
timeout=int(config.get("redis_timeout")),
decode_responses=False
)
return pyredis.StrictRedis(connection_pool=redis_pool)
# Let's just assume we got a StrictRedis-like object!
else:
return config_obj
elif attr.startswith("mongodb"):
if isinstance(config_obj, basestring):
if attr == "mongodb_logs" and config_obj == "1":
return connections.mongodb_jobs
elif config_obj == "0":
return None
from pymongo import MongoClient
mongo_parsed = pymongo.uri_parser.parse_uri(config_obj)
mongo_hosts = mongo_parsed["nodelist"]
mongo_name = mongo_parsed["database"]
log.debug("%s: Connecting to MongoDB at %s/%s..." % (attr, mongo_hosts, mongo_name))
kwargs = {}
db = MongoClient(config_obj, **kwargs)[mongo_name]
log.debug("%s: ... connected. (readPreference=%s)" % (attr, db.read_preference))
return db
# Let's just assume we got a MongoDB-like object!
else:
return config_obj
connections = LazyObject()
connections.add_factory(_connections_factory)
del _connections_factory
def enable_greenlet_tracing():
# Tracing seems to cause a 2-5% performance loss.
import greenlet
greenlet.GREENLET_USE_TRACING = True
def trace(*args):
time_since_last_switch = time.time() - trace.last_switch
# Record the time of the current switch
trace.last_switch = time.time()
if args[0] == "switch":
# We are switching from the greenlet args[1][0] to the greenlet
# args[1][1]
args[1][0].__dict__.setdefault("_trace_time", 0)
args[1][0].__dict__["_trace_time"] += time_since_last_switch
args[1][0].__dict__.setdefault("_trace_switches", 0)
args[1][0].__dict__["_trace_switches"] += 1
elif args[0] == "throw":
pass
trace.last_switch = time.time()
greenlet.settrace(trace) # pylint: disable=no-member
def subpool_map(pool_size, func, iterable):
""" Starts a Gevent pool and run a map. Takes care of setting current_job and cleaning up. """
if not pool_size:
return [func(*args) for args in iterable]
counter = itertools_count()
current_job = get_current_job()
def inner_func(*args):
""" As each call to 'func' will be done in a random greenlet of the subpool, we need to
register their IDs with set_current_job() to make get_current_job() calls work properly
inside 'func'.
"""
next(counter)
if current_job:
set_current_job(current_job)
try:
ret = func(*args)
except Exception as exc:
trace = traceback.format_exc()
log.error("Error in subpool: %s \n%s" % (exc, trace))
raise
if current_job:
set_current_job(None)
return ret
def inner_iterable():
""" This will be called inside the pool's main greenlet, which ID also needs to be registered """
if current_job:
set_current_job(current_job)
for x in iterable:
yield x
if current_job:
set_current_job(None)
start_time = time.time()
pool = gevent.pool.Pool(size=pool_size)
ret = pool.map(inner_func, inner_iterable())
pool.join(raise_error=True)
total_time = time.time() - start_time
log.debug("SubPool ran %s greenlets in %0.6fs" % (counter, total_time))
return ret
def subpool_imap(pool_size, func, iterable, flatten=False, unordered=False, buffer_size=None):
""" Generator version of subpool_map. Should be used with unordered=True for optimal performance """
if not pool_size:
for args in iterable:
yield func(*args)
counter = itertools_count()
current_job = get_current_job()
def inner_func(*args):
""" As each call to 'func' will be done in a random greenlet of the subpool, we need to
register their IDs with set_current_job() to make get_current_job() calls work properly
inside 'func'.
"""
next(counter)
if current_job:
set_current_job(current_job)
try:
ret = func(*args)
except Exception as exc:
trace = traceback.format_exc()
log.error("Error in subpool: %s \n%s" % (exc, trace))
raise
if current_job:
set_current_job(None)
return ret
def inner_iterable():
""" This will be called inside the pool's main greenlet, which ID also needs to be registered """
if current_job:
set_current_job(current_job)
for x in iterable:
yield x
if current_job:
set_current_job(None)
start_time = time.time()
pool = gevent.pool.Pool(size=pool_size)
if unordered:
iterator = pool.imap_unordered(inner_func, inner_iterable(), maxsize=buffer_size or pool_size)
else:
iterator = pool.imap(inner_func, inner_iterable())
for x in iterator:
if flatten:
for y in x:
yield y
else:
yield x
pool.join(raise_error=True)
total_time = time.time() - start_time
log.debug("SubPool ran %s greenlets in %0.6fs" % (counter, total_time))
def run_task(path, params):
""" Runs a task code synchronously """
task_class = load_class_by_path(path)
return task_class().run_wrapped(params)
def set_current_job_progress(ratio, save=False):
job = get_current_job()
if job:
job.set_progress(ratio, save=save)
# Imports for backward compatibility
def queue_raw_jobs(*args, **kwargs):
from . import job
return job.queue_raw_jobs(*args, **kwargs)
def queue_job(*args, **kwargs):
from . import job
return job.queue_job(*args, **kwargs)
def queue_jobs(*args, **kwargs):
from . import job
return job.queue_jobs(*args, **kwargs)
def metric(*args, **kwargs):
from . import helpers
return helpers.metric(*args, **kwargs)