##// END OF EJS Templates
reorganized a few files
MinRK -
Show More
@@ -1,100 +1,100 b''
1 """Thread for popping Tasks from zmq to Python Queue"""
1 """Thread for popping Tasks from zmq to Python Queue"""
2
2
3
3
4 import time
4 import time
5 from threading import Thread
5 from threading import Thread
6
6
7 try:
7 try:
8 from queue import Queue
8 from queue import Queue
9 except:
9 except:
10 from Queue import Queue
10 from Queue import Queue
11
11
12 import zmq
12 import zmq
13 from zmq.core.poll import _poll as poll
13 from zmq.core.poll import _poll as poll
14 from zmq.devices import ThreadDevice
14 from zmq.devices import ThreadDevice
15 from IPython.zmq import streamsession as ss
15 from IPython.zmq.parallel import streamsession as ss
16
16
17
17
18 class QueueStream(object):
18 class QueueStream(object):
19 def __init__(self, in_queue, out_queue):
19 def __init__(self, in_queue, out_queue):
20 self.in_queue = in_queue
20 self.in_queue = in_queue
21 self.out_queue = out_queue
21 self.out_queue = out_queue
22
22
23 def send_multipart(self, *args, **kwargs):
23 def send_multipart(self, *args, **kwargs):
24 while self.out_queue.full():
24 while self.out_queue.full():
25 time.sleep(1e-3)
25 time.sleep(1e-3)
26 self.out_queue.put(('send_multipart', args, kwargs))
26 self.out_queue.put(('send_multipart', args, kwargs))
27
27
28 def send(self, *args, **kwargs):
28 def send(self, *args, **kwargs):
29 while self.out_queue.full():
29 while self.out_queue.full():
30 time.sleep(1e-3)
30 time.sleep(1e-3)
31 self.out_queue.put(('send', args, kwargs))
31 self.out_queue.put(('send', args, kwargs))
32
32
33 def recv_multipart(self):
33 def recv_multipart(self):
34 return self.in_queue.get()
34 return self.in_queue.get()
35
35
36 def empty(self):
36 def empty(self):
37 return self.in_queue.empty()
37 return self.in_queue.empty()
38
38
39 class TaskThread(ThreadDevice):
39 class TaskThread(ThreadDevice):
40 """Class for popping Tasks from C-ZMQ->Python Queue"""
40 """Class for popping Tasks from C-ZMQ->Python Queue"""
41 max_qsize = 100
41 max_qsize = 100
42 in_socket = None
42 in_socket = None
43 out_socket = None
43 out_socket = None
44 # queue = None
44 # queue = None
45
45
46 def __init__(self, queue_type, mon_type, engine_id, max_qsize=100):
46 def __init__(self, queue_type, mon_type, engine_id, max_qsize=100):
47 ThreadDevice.__init__(self, 0, queue_type, mon_type)
47 ThreadDevice.__init__(self, 0, queue_type, mon_type)
48 self.session = ss.StreamSession(username='TaskNotifier[%s]'%engine_id)
48 self.session = ss.StreamSession(username='TaskNotifier[%s]'%engine_id)
49 self.engine_id = engine_id
49 self.engine_id = engine_id
50 self.in_queue = Queue(max_qsize)
50 self.in_queue = Queue(max_qsize)
51 self.out_queue = Queue(max_qsize)
51 self.out_queue = Queue(max_qsize)
52 self.max_qsize = max_qsize
52 self.max_qsize = max_qsize
53
53
54 @property
54 @property
55 def queues(self):
55 def queues(self):
56 return self.in_queue, self.out_queue
56 return self.in_queue, self.out_queue
57
57
58 @property
58 @property
59 def can_recv(self):
59 def can_recv(self):
60 # print self.in_queue.full(), poll((self.queue_socket, zmq.POLLIN),1e-3)
60 # print self.in_queue.full(), poll((self.queue_socket, zmq.POLLIN),1e-3)
61 return (not self.in_queue.full()) and poll([(self.queue_socket, zmq.POLLIN)], 1e-3 )
61 return (not self.in_queue.full()) and poll([(self.queue_socket, zmq.POLLIN)], 1e-3 )
62
62
63 @property
63 @property
64 def can_send(self):
64 def can_send(self):
65 return not self.out_queue.empty()
65 return not self.out_queue.empty()
66
66
67 def run(self):
67 def run(self):
68 print 'running'
68 print 'running'
69 self.queue_socket,self.mon_socket = self._setup_sockets()
69 self.queue_socket,self.mon_socket = self._setup_sockets()
70 print 'setup'
70 print 'setup'
71
71
72 while True:
72 while True:
73 while not self.can_send and not self.can_recv:
73 while not self.can_send and not self.can_recv:
74 # print 'idle'
74 # print 'idle'
75 # nothing to do, wait
75 # nothing to do, wait
76 time.sleep(1e-3)
76 time.sleep(1e-3)
77 while self.can_send:
77 while self.can_send:
78 # flush out queue
78 # flush out queue
79 print 'flushing...'
79 print 'flushing...'
80 meth, args, kwargs = self.out_queue.get()
80 meth, args, kwargs = self.out_queue.get()
81 getattr(self.queue_socket, meth)(*args, **kwargs)
81 getattr(self.queue_socket, meth)(*args, **kwargs)
82 print 'flushed'
82 print 'flushed'
83
83
84 if self.can_recv:
84 if self.can_recv:
85 print 'recving'
85 print 'recving'
86 # get another job from zmq
86 # get another job from zmq
87 msg = self.queue_socket.recv_multipart(0, copy=False)
87 msg = self.queue_socket.recv_multipart(0, copy=False)
88 # put it in the Queue
88 # put it in the Queue
89 self.in_queue.put(msg)
89 self.in_queue.put(msg)
90 idents,msg = self.session.feed_identities(msg, copy=False)
90 idents,msg = self.session.feed_identities(msg, copy=False)
91 msg = self.session.unpack_message(msg, content=False, copy=False)
91 msg = self.session.unpack_message(msg, content=False, copy=False)
92 # notify the Controller that we got it
92 # notify the Controller that we got it
93 self.mon_socket.send('tracktask', zmq.SNDMORE)
93 self.mon_socket.send('tracktask', zmq.SNDMORE)
94 header = msg['header']
94 header = msg['header']
95 msg_id = header['msg_id']
95 msg_id = header['msg_id']
96 content = dict(engine_id=self.engine_id, msg_id = msg_id)
96 content = dict(engine_id=self.engine_id, msg_id = msg_id)
97 self.session.send(self.mon_socket, 'task_receipt', content=content)
97 self.session.send(self.mon_socket, 'task_receipt', content=content)
98 print 'recvd'
98 print 'recvd'
99
99
100 No newline at end of file
100
@@ -1,271 +1,266 b''
1 .. _parallel_messages:
1 .. _parallel_messages:
2
2
3 Messaging for Parallel Computing
3 Messaging for Parallel Computing
4 ================================
4 ================================
5
5
6 This is an extension of the :ref:`messaging <messaging>` doc. Diagrams of the connections
6 This is an extension of the :ref:`messaging <messaging>` doc. Diagrams of the connections
7 can be found in the :ref:`parallel connections <parallel_connections>` doc.
7 can be found in the :ref:`parallel connections <parallel_connections>` doc.
8
8
9
9
10 ZMQ messaging is also used in the parallel computing IPython system. All messages to/from
10 ZMQ messaging is also used in the parallel computing IPython system. All messages to/from
11 kernels remain the same as the single kernel model, and are forwarded through a ZMQ Queue
11 kernels remain the same as the single kernel model, and are forwarded through a ZMQ Queue
12 device. The controller receives all messages and replies in these channels, and saves
12 device. The controller receives all messages and replies in these channels, and saves
13 results for future use.
13 results for future use.
14
14
15 The Controller
15 The Controller
16 --------------
16 --------------
17
17
18 The controller is the central process of the IPython parallel computing model. It has 3
18 The controller is the central process of the IPython parallel computing model. It has 3
19 Devices:
19 Devices:
20
20
21 * Heartbeater
21 * Heartbeater
22 * Multiplexed Queue
22 * Multiplexed Queue
23 * Task Queue
23 * Task Queue
24
24
25 and 3 sockets:
25 and 3 sockets:
26
26
27 * ``XREP`` for both engine and client registration
27 * ``XREP`` for both engine and client registration
28 * ``PUB`` for notification of engine changes
28 * ``PUB`` for notification of engine changes
29 * ``XREP`` for client requests
29 * ``XREP`` for client requests
30
30
31
31
32
32
33 Registration (``XREP``)
33 Registration (``XREP``)
34 ***********************
34 ***********************
35
35
36 The first function of the Controller is to facilitate and monitor connections of clients
36 The first function of the Controller is to facilitate and monitor connections of clients
37 and engines. Both client and engine registration are handled by the same socket, so only
37 and engines. Both client and engine registration are handled by the same socket, so only
38 one ip/port pair is needed to connect any number of connections and clients.
38 one ip/port pair is needed to connect any number of connections and clients.
39
39
40 Engines register with the ``zmq.IDENTITY`` of their two ``XREQ`` sockets, one for the
40 Engines register with the ``zmq.IDENTITY`` of their two ``XREQ`` sockets, one for the
41 queue, which receives execute requests, and one for the heartbeat, which is used to
41 queue, which receives execute requests, and one for the heartbeat, which is used to
42 monitor the survival of the Engine process.
42 monitor the survival of the Engine process.
43
43
44 Message type: ``registration_request``::
44 Message type: ``registration_request``::
45
45
46 content = {
46 content = {
47 'queue' : 'abcd-1234-...', # the queue XREQ id
47 'queue' : 'abcd-1234-...', # the queue XREQ id
48 'heartbeat' : '1234-abcd-...' # the heartbeat XREQ id
48 'heartbeat' : '1234-abcd-...' # the heartbeat XREQ id
49 }
49 }
50
50
51 The Controller replies to an Engine's registration request with the engine's integer ID,
51 The Controller replies to an Engine's registration request with the engine's integer ID,
52 and all the remaining connection information for connecting the heartbeat process, and
52 and all the remaining connection information for connecting the heartbeat process, and
53 kernel queue socket(s). The message status will be an error if the Engine requests IDs that
53 kernel queue socket(s). The message status will be an error if the Engine requests IDs that
54 already in use.
54 already in use.
55
55
56 Message type: ``registration_reply``::
56 Message type: ``registration_reply``::
57
57
58 content = {
58 content = {
59 'status' : 'ok', # or 'error'
59 'status' : 'ok', # or 'error'
60 # if ok:
60 # if ok:
61 'id' : 0, # int, the engine id
61 'id' : 0, # int, the engine id
62 'queue' : 'tcp://127.0.0.1:12345', # connection for engine side of the queue
62 'queue' : 'tcp://127.0.0.1:12345', # connection for engine side of the queue
63 'control' : 'tcp://...', # addr for control queue
63 'control' : 'tcp://...', # addr for control queue
64 'heartbeat' : (a,b), # tuple containing two interfaces needed for heartbeat
64 'heartbeat' : (a,b), # tuple containing two interfaces needed for heartbeat
65 'task' : 'tcp://...', # addr for task queue, or None if no task queue running
65 'task' : 'tcp://...', # addr for task queue, or None if no task queue running
66 # if error:
67 'reason' : 'queue_id already registered'
68 }
66 }
69
67
70 Clients use the same socket as engines to start their connections. Connection requests
68 Clients use the same socket as engines to start their connections. Connection requests
71 from clients need no information:
69 from clients need no information:
72
70
73 Message type: ``connection_request``::
71 Message type: ``connection_request``::
74
72
75 content = {}
73 content = {}
76
74
77 The reply to a Client registration request contains the connection information for the
75 The reply to a Client registration request contains the connection information for the
78 multiplexer and load balanced queues, as well as the address for direct controller
76 multiplexer and load balanced queues, as well as the address for direct controller
79 queries. If any of these addresses is `None`, that functionality is not available.
77 queries. If any of these addresses is `None`, that functionality is not available.
80
78
81 Message type: ``connection_reply``::
79 Message type: ``connection_reply``::
82
80
83 content = {
81 content = {
84 'status' : 'ok', # or 'error'
82 'status' : 'ok', # or 'error'
85 # if ok:
83 # if ok:
86 'queue' : 'tcp://127.0.0.1:12345', # connection for client side of the MUX queue
84 'queue' : 'tcp://127.0.0.1:12345', # connection for client side of the MUX queue
87 'task' : 'tcp...', # addr for task queue, or None if no task queue running
85 'task' : 'tcp...', # addr for task queue, or None if no task queue running
88 'query' : 'tcp...' # addr for methods to query the controller, like queue_request, etc.
86 'query' : 'tcp...' # addr for methods to query the controller, like queue_request, etc.
89 'control' : 'tcp...' # addr for control methods, like abort, etc.
87 'control' : 'tcp...' # addr for control methods, like abort, etc.
90 }
88 }
91
89
92 Heartbeat
90 Heartbeat
93 *********
91 *********
94
92
95 The controller uses a heartbeat system to monitor engines, and track when they become
93 The controller uses a heartbeat system to monitor engines, and track when they become
96 unresponsive. As described in :ref:`messages <messages>`, and shown in :ref:`connections
94 unresponsive. As described in :ref:`messages <messages>`, and shown in :ref:`connections
97 <parallel_connections>`.
95 <parallel_connections>`.
98
96
99 Notification (``PUB``)
97 Notification (``PUB``)
100 **********************
98 **********************
101
99
102 The controller published all engine registration/unregistration events on a PUB socket.
100 The controller published all engine registration/unregistration events on a PUB socket.
103 This allows clients to have up-to-date engine ID sets without polling. Registration
101 This allows clients to have up-to-date engine ID sets without polling. Registration
104 notifications contain both the integer engine ID and the queue ID, which is necessary for
102 notifications contain both the integer engine ID and the queue ID, which is necessary for
105 sending messages via the Multiplexer Queue.
103 sending messages via the Multiplexer Queue.
106
104
107 Message type: ``registration_notification``::
105 Message type: ``registration_notification``::
108
106
109 content = {
107 content = {
110 'id' : 0, # engine ID that has been registered
108 'id' : 0, # engine ID that has been registered
111 'queue' : 'engine_id' # the IDENT for the engine's queue
109 'queue' : 'engine_id' # the IDENT for the engine's queue
112 }
110 }
113
111
114 Message type : ``unregistration_notification``::
112 Message type : ``unregistration_notification``::
115
113
116 content = {
114 content = {
117 'id' : 0 # engine ID that has been unregistered
115 'id' : 0 # engine ID that has been unregistered
118 }
116 }
119
117
120
118
121 Client Queries (``XREP``)
119 Client Queries (``XREP``)
122 *************************
120 *************************
123
121
124 The controller monitors and logs all queue traffic, so that clients can retrieve past
122 The controller monitors and logs all queue traffic, so that clients can retrieve past
125 results or monitor pending tasks. Currently, this information resides in memory on the
123 results or monitor pending tasks. Currently, this information resides in memory on the
126 Controller, but will ultimately be offloaded to a database over an additional ZMQ
124 Controller, but will ultimately be offloaded to a database over an additional ZMQ
127 connection. The interface should remain the same or at least similar.
125 connection. The interface should remain the same or at least similar.
128
126
129 :func:`queue_request` requests can specify multiple engines to query via the `targets`
127 :func:`queue_request` requests can specify multiple engines to query via the `targets`
130 element. A verbose flag can be passed, to determine whether the result should be the list
128 element. A verbose flag can be passed, to determine whether the result should be the list
131 of `msg_ids` in the queue or simply the length of each list.
129 of `msg_ids` in the queue or simply the length of each list.
132
130
133 Message type: ``queue_request``::
131 Message type: ``queue_request``::
134
132
135 content = {
133 content = {
136 'verbose' : True, # whether return should be lists themselves or just lens
134 'verbose' : True, # whether return should be lists themselves or just lens
137 'targets' : [0,3,1] # list of ints
135 'targets' : [0,3,1] # list of ints
138 }
136 }
139
137
140 The content of a reply to a :func:queue_request request is a dict, keyed by the engine
138 The content of a reply to a :func:queue_request request is a dict, keyed by the engine
141 IDs. Note that they will be the string representation of the integer keys, since JSON
139 IDs. Note that they will be the string representation of the integer keys, since JSON
142 cannot handle number keys.
140 cannot handle number keys.
143
141
144 Message type: ``queue_reply``::
142 Message type: ``queue_reply``::
145
143
146 content = {
144 content = {
147 '0' : {'completed' : 1, 'queue' : 7},
145 '0' : {'completed' : 1, 'queue' : 7},
148 '1' : {'completed' : 10, 'queue' : 1}
146 '1' : {'completed' : 10, 'queue' : 1}
149 }
147 }
150
148
151 Clients can request individual results directly from the controller. This is primarily for
149 Clients can request individual results directly from the controller. This is primarily for
152 use gathering results of executions not submitted by the particular client, as the client
150 use gathering results of executions not submitted by the particular client, as the client
153 will have all its own results already. Requests are made by msg_id, and can contain one or
151 will have all its own results already. Requests are made by msg_id, and can contain one or
154 more msg_id.
152 more msg_id.
155
153
156 Message type: ``result_request``::
154 Message type: ``result_request``::
157
155
158 content = {
156 content = {
159 'msg_ids' : ['uuid','...'] # list of strs
157 'msg_ids' : ['uuid','...'] # list of strs
160 }
158 }
161
159
162 The :func:`result_request` reply contains the content objects of the actual execution
160 The :func:`result_request` reply contains the content objects of the actual execution
163 reply messages
161 reply messages
164
162
165
163
166 Message type: ``result_reply``::
164 Message type: ``result_reply``::
167
165
168 content = {
166 content = {
169 'status' : 'ok', # else error
167 'status' : 'ok', # else error
170 # if ok:
168 # if ok:
171 msg_id : msg, # the content dict is keyed by msg_ids,
169 msg_id : msg, # the content dict is keyed by msg_ids,
172 # values are the result messages
170 # values are the result messages
173 'pending' : ['msg_id','...'], # msg_ids still pending
171 'pending' : ['msg_id','...'], # msg_ids still pending
174 'completed' : ['msg_id','...'], # list of completed msg_ids
172 'completed' : ['msg_id','...'], # list of completed msg_ids
175 # if error:
176 'reason' : "explanation"
177 }
173 }
178
174
179 For memory management purposes, Clients can also instruct the controller to forget the
175 For memory management purposes, Clients can also instruct the controller to forget the
180 results of messages. This can be done by message ID or engine ID. Individual messages are
176 results of messages. This can be done by message ID or engine ID. Individual messages are
181 dropped by msg_id, and all messages completed on an engine are dropped by engine ID. This will likely no longer
177 dropped by msg_id, and all messages completed on an engine are dropped by engine ID. This will likely no longer
182 be necessary once we move to a DB-based message logging backend.
178 be necessary once we move to a DB-based message logging backend.
183
179
184 If the msg_ids element is the string ``'all'`` instead of a list, then all completed
180 If the msg_ids element is the string ``'all'`` instead of a list, then all completed
185 results are forgotten.
181 results are forgotten.
186
182
187 Message type: ``purge_request``::
183 Message type: ``purge_request``::
188
184
189 content = {
185 content = {
190 'msg_ids' : ['id1', 'id2',...], # list of msg_ids or 'all'
186 'msg_ids' : ['id1', 'id2',...], # list of msg_ids or 'all'
191 'engine_ids' : [0,2,4] # list of engine IDs
187 'engine_ids' : [0,2,4] # list of engine IDs
192 }
188 }
193
189
194 The reply to a purge request is simply the status 'ok' if the request succeeded, or an
190 The reply to a purge request is simply the status 'ok' if the request succeeded, or an
195 explanation of why it failed, such as requesting the purge of a nonexistent or pending
191 explanation of why it failed, such as requesting the purge of a nonexistent or pending
196 message.
192 message.
197
193
198 Message type: ``purge_reply``::
194 Message type: ``purge_reply``::
199
195
200 content = {
196 content = {
201 'status' : 'ok', # or 'error'
197 'status' : 'ok', # or 'error'
202
203 # if error:
204 'reason' : "KeyError: no such msg_id 'whoda'"
205 }
198 }
206
199
207 :func:`apply` and :func:`apply_bound`
200 :func:`apply` and :func:`apply_bound`
208 *************************************
201 *************************************
209
202
210 The `Namespace <http://gist.github.com/483294>`_ model suggests that execution be able to
203 The `Namespace <http://gist.github.com/483294>`_ model suggests that execution be able to
211 use the model::
204 use the model::
212
205
213 client.apply(f, *args, **kwargs)
206 ns.apply(f, *args, **kwargs)
214
207
215 which takes `f`, a function in the user's namespace, and executes ``f(*args, **kwargs)``
208 which takes `f`, a function in the user's namespace, and executes ``f(*args, **kwargs)``
216 on a remote engine, returning the result (or, for non-blocking, information facilitating
209 on a remote engine, returning the result (or, for non-blocking, information facilitating
217 later retrieval of the result). This model, unlike the execute message which just uses a
210 later retrieval of the result). This model, unlike the execute message which just uses a
218 code string, must be able to send arbitrary (pickleable) Python objects. And ideally, copy
211 code string, must be able to send arbitrary (pickleable) Python objects. And ideally, copy
219 as little data as we can. The `buffers` property of a Message was introduced for this
212 as little data as we can. The `buffers` property of a Message was introduced for this
220 purpose.
213 purpose.
221
214
222 Utility method :func:`build_apply_message` in :mod:`IPython.zmq.streamsession` wraps a
215 Utility method :func:`build_apply_message` in :mod:`IPython.zmq.streamsession` wraps a
223 function signature and builds the correct buffer format for minimal data copying (exactly
216 function signature and builds a sendable buffer format for minimal data copying (exactly
224 zero copies of numpy array data).
217 zero copies of numpy array data or buffers or large strings).
225
218
226 Message type: ``apply_request``::
219 Message type: ``apply_request``::
227
220
228 content = {
221 content = {
229 'bound' : True, # whether to execute in the engine's namespace or unbound
222 'bound' : True, # whether to execute in the engine's namespace or unbound
230 'after' : [msg_ids,...], # list of msg_ids or output of Dependency.as_dict()
223 'after' : [msg_ids,...], # list of msg_ids or output of Dependency.as_dict()
231 'follow' : [msg_ids,...], # list of msg_ids or output of Dependency.as_dict()
224 'follow' : [msg_ids,...], # list of msg_ids or output of Dependency.as_dict()
232
225
233 }
226 }
234 buffers = ['...'] # at least 3 in length
227 buffers = ['...'] # at least 3 in length
235 # as built by build_apply_message(f,args,kwargs)
228 # as built by build_apply_message(f,args,kwargs)
236
229
230 after/follow represent task dependencies
231
237 Message type: ``apply_reply``::
232 Message type: ``apply_reply``::
238
233
239 content = {
234 content = {
240 'status' : 'ok' # 'ok' or 'error'
235 'status' : 'ok' # 'ok' or 'error'
241 # other error info here, as in other messages
236 # other error info here, as in other messages
242 }
237 }
243 buffers = ['...'] # either 1 or 2 in length
238 buffers = ['...'] # either 1 or 2 in length
244 # a serialization of the return value of f(*args,**kwargs)
239 # a serialization of the return value of f(*args,**kwargs)
245 # only populated if status is 'ok'
240 # only populated if status is 'ok'
246
241
247
242
248
243
249
244
250 Implementation
245 Implementation
251 --------------
246 --------------
252
247
253 There are a few differences in implementation between the `StreamSession` object used in
248 There are a few differences in implementation between the `StreamSession` object used in
254 the parallel computing fork and the `Session` object, the main one being that messages are
249 the parallel computing fork and the `Session` object, the main one being that messages are
255 sent in parts, rather than as a single serialized object. `StreamSession` objects also
250 sent in parts, rather than as a single serialized object. `StreamSession` objects also
256 take pack/unpack functions, which are to be used when serializing/deserializing objects.
251 take pack/unpack functions, which are to be used when serializing/deserializing objects.
257 These can be any functions that translate to/from formats that ZMQ sockets can send
252 These can be any functions that translate to/from formats that ZMQ sockets can send
258 (buffers,bytes, etc.).
253 (buffers,bytes, etc.).
259
254
260 Split Sends
255 Split Sends
261 ***********
256 ***********
262
257
263 Previously, messages were bundled as a single json object and one call to
258 Previously, messages were bundled as a single json object and one call to
264 :func:`socket.send_json`. Since the controller inspects all messages, and doesn't need to
259 :func:`socket.send_json`. Since the controller inspects all messages, and doesn't need to
265 see the content of the messages, which can be large, messages are now serialized and sent in
260 see the content of the messages, which can be large, messages are now serialized and sent in
266 pieces. All messages are sent in at least 3 parts: the header, the parent header, and the
261 pieces. All messages are sent in at least 3 parts: the header, the parent header, and the
267 content. This allows the controller to unpack and inspect the (always small) header,
262 content. This allows the controller to unpack and inspect the (always small) header,
268 without spending time unpacking the content unless the message is bound for the
263 without spending time unpacking the content unless the message is bound for the
269 controller. Buffers are added on to the end of the message, and can be any objects that
264 controller. Buffers are added on to the end of the message, and can be any objects that
270 present the buffer interface.
265 present the buffer interface.
271
266
1 NO CONTENT: file renamed from examples/demo/dagdeps.py to examples/demo/dag/dagdeps.py
NO CONTENT: file renamed from examples/demo/dagdeps.py to examples/demo/dag/dagdeps.py
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now