1
1
# Copyright © 2022 Ingram Micro Inc. All rights reserved.
2
- import multiprocessing
2
+ import logging
3
3
import signal
4
+ import threading
5
+ from pathlib import Path
4
6
5
7
from django .core .management .base import BaseCommand , CommandError
8
+ from watchfiles import watch
9
+ from watchfiles .filters import PythonFilter
10
+ from watchfiles .run import start_process
6
11
7
12
from dj_cqrs .registries import ReplicaRegistry
8
- from dj_cqrs .transport import current_transport
13
+
14
+
15
+ logger = logging .getLogger ('django_cqrs.cqrs_consume' )
16
+
17
+
18
+ def consume (** kwargs ):
19
+ import django
20
+ django .setup ()
21
+
22
+ from dj_cqrs .transport import current_transport
23
+ current_transport .consume (** kwargs )
9
24
10
25
11
26
class WorkersManager :
12
27
13
- def __init__ (self , options , transport , consume_kwargs ):
28
+ def __init__ (
29
+ self ,
30
+ consume_kwargs ,
31
+ workers = 1 ,
32
+ reload = False ,
33
+ ignore_paths = None ,
34
+ sigint_timeout = 5 ,
35
+ sigkill_timeout = 1 ,
36
+ ):
14
37
self .pool = []
15
- self .options = options
16
- self .transport = transport
38
+ self .workers = workers
39
+ self .reload = reload
17
40
self .consume_kwargs = consume_kwargs
41
+ self .stop_event = threading .Event ()
42
+ self .sigint_timeout = sigint_timeout
43
+ self .sigkill_timeout = sigkill_timeout
44
+
45
+ if self .reload :
46
+ self .watch_filter = PythonFilter (ignore_paths = ignore_paths )
47
+ self .watcher = watch (
48
+ Path .cwd (),
49
+ watch_filter = self .watch_filter ,
50
+ stop_event = self .stop_event ,
51
+ yield_on_timeout = True ,
52
+ )
53
+
54
+ def handle_signal (self , * args , ** kwargs ):
55
+ self .stop_event .set ()
56
+
57
+ def run (self ):
58
+ for sig in [signal .SIGINT , signal .SIGTERM ]:
59
+ signal .signal (sig , self .handle_signal )
60
+ if self .reload :
61
+ signal .signal (signal .SIGHUP , self .restart )
62
+
63
+ self .start ()
64
+
65
+ if self .reload :
66
+ for files_changed in self :
67
+ if files_changed :
68
+ self .restart ()
69
+ else :
70
+ self .stop_event .wait ()
71
+
72
+ self .terminate ()
18
73
19
74
def start (self ):
20
- for i in range (self .options ['workers' ] or 1 ):
21
- process = multiprocessing .Process (
22
- name = f'cqrs-consumer-{ i } ' ,
23
- target = self .transport .consume ,
24
- kwargs = self .consume_kwargs ,
75
+ for _ in range (self .workers ):
76
+ process = start_process (
77
+ consume ,
78
+ 'function' ,
79
+ (),
80
+ self .consume_kwargs ,
25
81
)
26
82
self .pool .append (process )
27
- process .start ()
28
-
29
- for process in self .pool :
30
- process .join ()
31
83
32
84
def terminate (self , * args , ** kwargs ):
33
85
while self .pool :
34
- p = self .pool .pop ()
35
- p .terminate ()
36
- p .join ()
86
+ process = self .pool .pop ()
87
+ process .stop (sigint_timeout = self .sigint_timeout , sigkill_timeout = self .sigkill_timeout )
37
88
38
- def reload (self , * args , ** kwargs ):
89
+ def restart (self , * args , ** kwargs ):
39
90
self .terminate ()
40
91
self .start ()
41
92
93
+ def __iter__ (self ):
94
+ return self
95
+
96
+ def __next__ (self ):
97
+ changes = next (self .watcher )
98
+ if changes :
99
+ return list ({Path (c [1 ]) for c in changes })
100
+ return None
101
+
42
102
43
103
class Command (BaseCommand ):
44
104
help = 'Starts CQRS worker, which consumes messages from message queue.'
45
105
46
106
def add_arguments (self , parser ):
47
- parser .add_argument ('--workers' , '-w' , help = 'Number of workers' , type = int , default = 0 )
107
+ parser .add_argument (
108
+ '--workers' ,
109
+ '-w' ,
110
+ help = 'Number of workers' ,
111
+ type = int ,
112
+ default = 1 ,
113
+ )
48
114
parser .add_argument (
49
115
'--cqrs-id' ,
50
116
'-cid' ,
@@ -53,35 +119,72 @@ def add_arguments(self, parser):
53
119
help = 'Choose model(s) by CQRS_ID for consuming' ,
54
120
)
55
121
parser .add_argument (
56
- '--reload' , '-r' , help = 'Enable reload signal SIGHUP' , action = 'store_true' ,
122
+ '--reload' ,
123
+ '-r' ,
124
+ help = (
125
+ 'Enable reload signal SIGHUP and autoreload '
126
+ 'on file changes'
127
+ ),
128
+ action = 'store_true' ,
129
+ default = False ,
130
+ )
131
+ parser .add_argument (
132
+ '--ignore-paths' ,
133
+ nargs = '?' ,
134
+ type = str ,
135
+ help = (
136
+ 'Specify directories to ignore, '
137
+ 'to ignore multiple paths use a comma as separator, '
138
+ 'e.g. "env" or "env,node_modules"'
139
+ ),
140
+ )
141
+ parser .add_argument (
142
+ '--sigint-timeout' ,
143
+ nargs = '?' ,
144
+ type = int ,
145
+ default = 5 ,
146
+ help = 'How long to wait for the sigint timeout before sending sigkill.' ,
147
+ )
148
+ parser .add_argument (
149
+ '--sigkill-timeout' ,
150
+ nargs = '?' ,
151
+ type = int ,
152
+ default = 1 ,
153
+ help = 'How long to wait for the sigkill timeout before issuing a timeout exception.' ,
57
154
)
58
155
59
- def handle (self , * args , ** options ):
60
- if not options ['workers' ] and not options ['reload' ]:
61
- current_transport .consume (** self .get_consume_kwargs (options ))
62
- return
63
-
64
- self .start_workers_pool (options )
156
+ def handle (
157
+ self ,
158
+ * args ,
159
+ workers = 1 ,
160
+ cqrs_id = None ,
161
+ reload = False ,
162
+ ignore_paths = None ,
163
+ sigint_timeout = 5 ,
164
+ sigkill_timeout = 1 ,
165
+ ** options ,
166
+ ):
167
+
168
+ paths_to_ignore = None
169
+ if ignore_paths :
170
+ paths_to_ignore = [Path (p ).resolve () for p in ignore_paths .split (',' )]
65
171
66
- def start_workers_pool (self , options ):
67
172
workers_manager = WorkersManager (
68
- options , current_transport , self .get_consume_kwargs (options ),
173
+ workers = workers ,
174
+ consume_kwargs = self .get_consume_kwargs (cqrs_id ),
175
+ reload = reload ,
176
+ ignore_paths = paths_to_ignore ,
177
+ sigint_timeout = sigint_timeout ,
178
+ sigkill_timeout = sigkill_timeout ,
69
179
)
70
- if options ['reload' ]:
71
- try :
72
- multiprocessing .set_start_method ('spawn' )
73
- except RuntimeError :
74
- pass
75
-
76
- signal .signal (signal .SIGHUP , workers_manager .reload )
77
180
78
- workers_manager .start ()
181
+ workers_manager .run ()
79
182
80
- def get_consume_kwargs (self , options ):
183
+ def get_consume_kwargs (self , ids_list ):
81
184
consume_kwargs = {}
82
- if options . get ( 'cqrs_id' ) :
185
+ if ids_list :
83
186
cqrs_ids = set ()
84
- for cqrs_id in options [ 'cqrs_id' ] :
187
+ for cqrs_id in ids_list :
85
188
model = ReplicaRegistry .get_model_by_cqrs_id (cqrs_id )
86
189
if not model :
87
190
raise CommandError ('Wrong CQRS ID: {0}!' .format (cqrs_id ))
0 commit comments