25
25
except :
26
26
blockingError = OSError
27
27
28
+ import threading
29
+
30
+ lock = threading .Lock ()
31
+
28
32
29
33
class PipeUnavailableError (Exception ):
30
34
"""raised when unable to write to pipe given allotted time"""
@@ -113,16 +117,16 @@ def kill(self):
113
117
except :
114
118
pass
115
119
116
- def send (self , msg , retries = 3 ):
120
+ def send (self , msg , retries = 3 , thread_safe_send = False ):
117
121
if msg .msg_type == MessageTypes .MUST_SEND :
118
122
# If this is a must-send message, we treat it a bit differently. A must-send
119
123
# message has to be properly sent before any of the other best effort messages.
120
124
self ._cached_mustsend = msg .payload
121
125
self ._send_mustsend_remaining_tries = MUST_SEND_RETRY_TIMES
122
- self ._send_mustsend (retries )
126
+ self ._send_mustsend (retries , thread_safe_send )
123
127
else :
124
128
# Ignore return code for send.
125
- self ._send_internal (msg , retries = retries )
129
+ self ._send_internal (msg , retries = retries , thread_safe_send = thread_safe_send )
126
130
127
131
def _start_subprocess (self , cmdline ):
128
132
for _ in range (3 ):
@@ -145,7 +149,7 @@ def _start_subprocess(self, cmdline):
145
149
self ._logger ("Unknown popen error: %s" % repr (e ))
146
150
break
147
151
148
- def _send_internal (self , msg , retries = 3 ):
152
+ def _send_internal (self , msg , retries = 3 , thread_safe_send = False ):
149
153
if self ._process is None :
150
154
return False
151
155
try :
@@ -157,13 +161,13 @@ def _send_internal(self, msg, retries=3):
157
161
# restart sidecar so use the PipeUnavailableError caught below
158
162
raise PipeUnavailableError ()
159
163
elif self ._send_mustsend_remaining_tries > 0 :
160
- self ._send_mustsend ()
164
+ self ._send_mustsend (thread_safe_send = thread_safe_send )
161
165
if self ._send_mustsend_remaining_tries == 0 :
162
- self ._emit_msg (msg )
166
+ self ._emit_msg (msg , thread_safe_send )
163
167
self ._prev_message_error = False
164
168
return True
165
169
else :
166
- self ._emit_msg (msg )
170
+ self ._emit_msg (msg , thread_safe_send )
167
171
self ._prev_message_error = False
168
172
return True
169
173
return False
@@ -184,22 +188,24 @@ def _send_internal(self, msg, retries=3):
184
188
self ._prev_message_error = True
185
189
if retries > 0 :
186
190
self ._logger ("Retrying msg send to sidecar (due to %s)" % repr (ex ))
187
- return self ._send_internal (msg , retries - 1 )
191
+ return self ._send_internal (msg , retries - 1 , thread_safe_send )
188
192
else :
189
193
self ._logger (
190
194
"Error sending log message (exhausted retries): %s" % repr (ex )
191
195
)
192
196
return False
193
197
194
- def _send_mustsend (self , retries = 3 ):
198
+ def _send_mustsend (self , retries = 3 , thread_safe_send = False ):
195
199
if (
196
200
self ._cached_mustsend is not None
197
201
and self ._send_mustsend_remaining_tries > 0
198
202
):
199
203
# If we don't succeed in sending the must-send, we will try again
200
204
# next time.
201
205
if self ._send_internal (
202
- Message (MessageTypes .MUST_SEND , self ._cached_mustsend ), retries
206
+ Message (MessageTypes .MUST_SEND , self ._cached_mustsend ),
207
+ retries ,
208
+ thread_safe_send ,
203
209
):
204
210
self ._cached_mustsend = None
205
211
self ._send_mustsend_remaining_tries = 0
@@ -211,14 +217,7 @@ def _send_mustsend(self, retries=3):
211
217
self ._send_mustsend_remaining_tries = - 1
212
218
return False
213
219
214
- def _emit_msg (self , msg ):
215
- # If the previous message had an error, we want to prepend a "\n" to this message
216
- # to maximize the chance of this message being valid (for example, if the
217
- # previous message only partially sent for whatever reason, we want to "clear" it)
218
- msg = msg .serialize ()
219
- if self ._prev_message_error :
220
- msg = "\n " + msg
221
- msg_ser = msg .encode ("utf-8" )
220
+ def _write_bytes (self , msg_ser ):
222
221
written_bytes = 0
223
222
while written_bytes < len (msg_ser ):
224
223
# self._logger("Sent %d out of %d bytes" % (written_bytes, len(msg_ser)))
@@ -235,6 +234,23 @@ def _emit_msg(self, msg):
235
234
# sidecar is disabled, ignore all messages
236
235
break
237
236
237
+ def _emit_msg (self , msg , thread_safe_send = False ):
238
+ # If the previous message had an error, we want to prepend a "\n" to this message
239
+ # to maximize the chance of this message being valid (for example, if the
240
+ # previous message only partially sent for whatever reason, we want to "clear" it)
241
+ msg = msg .serialize ()
242
+ if self ._prev_message_error :
243
+ msg = "\n " + msg
244
+ msg_ser = msg .encode ("utf-8" )
245
+
246
+ # If threadsafe send is enabled, we will use a lock to ensure that only one thread
247
+ # can send a message at a time. This is to avoid interleaving of messages.
248
+ if thread_safe_send :
249
+ with lock :
250
+ self ._write_bytes (msg_ser )
251
+ else :
252
+ self ._write_bytes (msg_ser )
253
+
238
254
def _logger (self , msg ):
239
255
if debug .sidecar :
240
256
print ("[sidecar:%s] %s" % (self ._worker_type , msg ), file = sys .stderr )
0 commit comments