Skip to content

Commit 631b585

Browse files
author
Istemi Ekin Akkus
authored
Merge pull request #76 from knix-microfunctions/release/0.8.6
Release/0.8.6
2 parents 5d7a8f1 + 4752e3f commit 631b585

File tree

121 files changed

+2163
-2514
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

121 files changed

+2163
-2514
lines changed

DataLayerService/src/main/java/org/microfunctions/data_layer/RiakAccess.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,12 @@ public AbstractMap.SimpleEntry<String, ByteBuffer> selectRow (String keyspace, S
578578
}
579579

580580
ByteBuffer value = ByteBuffer.wrap(object.getValue().unsafeGetValue());
581+
582+
if (value.hasArray() && value.array().length == 1 && value.array()[0] == 0)
583+
{
584+
value = ByteBuffer.wrap(new byte[] {});
585+
}
586+
581587
return new AbstractMap.SimpleEntry<String, ByteBuffer>(key, value);
582588
} catch (Exception e) {
583589
LOGGER.error("selectRow() failed. Keyspace: " + keyspace + " Table: " + table, e);

FunctionWorker/.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
data_layer/
2-
local_queue/
1+
/python/data_layer/
2+
/python/local_queue/
3+
/doc/
34
__init__.py
5+
__pycache__

FunctionWorker/Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ include ../build_env.mk
1919
UID:=`id -u`
2020
GID:=`id -g`
2121

22-
default: python_api_doc
22+
default: doc/MicroFunctionsAPI.html
2323

24-
thrift_data_layer: ../build_env.mk \
24+
PYTHON_DATALAYER=$(shell find python/data_layer -type f -size +1c) python/data_layer
25+
$(PYTHON_DATALAYER): ../build_env.mk \
2526
../DataLayerService/thrift/DataLayerMessage.thrift \
2627
../DataLayerService/thrift/DataLayerService.thrift
2728
docker run --user $(UID):$(GID) --rm -v $(CURDIR)/..:/root -w /root $(THRIFT_IMAGE_NAME):$(THRIFT_VERSION) bash -c '\
2829
thrift --gen py -out FunctionWorker/python/ DataLayerService/thrift/DataLayerMessage.thrift; \
2930
thrift --gen py -out FunctionWorker/python/ DataLayerService/thrift/DataLayerService.thrift'
3031

31-
python_api_doc: thrift_data_layer
32+
doc/MicroFunctionsAPI.html: $(PYTHON_DATALAYER)
3233
docker run --user $(UID):$(GID) --rm -it \
3334
--name mfnapi_doc_gen \
3435
-v $(CURDIR):/opt/mfnapi \
3536
-w /opt/mfnapi \
37+
-e http_proxy \
38+
-e https_proxy \
3639
python:3.6 \
3740
/bin/bash -c '\
3841
pip3 install pdoc3 --target /opt/mfnapi/tmppip/pdoc3; \

FunctionWorker/python/DataLayerClient.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,25 @@ def createTriggerableTable(self, tableName):
566566
raise
567567
return status
568568

569+
def listKeys(self, start, count, tableName=None):
570+
listkeys_response = []
571+
table = self.tablename if tableName is None else tableName
572+
573+
for retry in range(MAX_RETRIES):
574+
try:
575+
listkeys_response = self.datalayer.selectKeys(self.keyspace, table, start, count, self.locality)
576+
if listkeys_response == None or type(listkeys_response) != type([]):
577+
listkeys_response = []
578+
break
579+
except TTransport.TTransportException as exc:
580+
print("[DataLayerClient] Reconnecting because of failed selectKeys: " + str(exc))
581+
self.connect()
582+
except Exception as exc:
583+
print("[DataLayerClient] failed selectKeys: " + str(exc))
584+
raise
585+
586+
return listkeys_response
587+
569588
def shutdown(self):
570589
try:
571590
self.transport.close()

FunctionWorker/python/MicroFunctionsAPI.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def send_to_running_function_in_session_with_alias(self, alias, message, send_no
253253
else:
254254
self._logger.warning("Cannot send a session update message in a workflow with no session functions.")
255255

256-
def get_session_update_messages(self, count=1):
256+
def get_session_update_messages(self, count=1, block=False):
257257
'''
258258
Retrieve the list of update messages sent to a session function instance.
259259
The list contains messages that were sent and delivered since the last time the session function instance has retrieved it.
@@ -263,9 +263,11 @@ def get_session_update_messages(self, count=1):
263263
264264
Args:
265265
count (int): the number of messages to retrieve; default: 1
266+
block (boolean): whether it should block until 'count' number of messages have been received.
266267
267268
Returns:
268269
List of messages that were sent to the session function instance.
270+
If in non-blocking mode, the number of returned messages will be less than or equal to 'count'.
269271
270272
Warns:
271273
When the calling function is not a session function.
@@ -278,7 +280,7 @@ def get_session_update_messages(self, count=1):
278280
messages = []
279281
if self._is_session_function:
280282
#self._logger.debug("[MicroFunctionsAPI] getting session update messages...")
281-
messages = self._session_utils.get_session_update_messages_with_local_queue(count)
283+
messages = self._session_utils.get_session_update_messages_with_local_queue(count=count, block=block)
282284
else:
283285
self._logger.warning("Cannot get session update messages in a non-session function: " + self._function_state_name)
284286

FunctionWorker/python/MicroFunctionsLogWriter.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@ def __init__(self, logger, level=logging.DEBUG):
2727

2828
def write(self, msg):
2929
msg = msg.rstrip()
30-
lines = msg.splitlines()
31-
for line in lines:
32-
line = line.rstrip()
33-
self._logger.log(self._level, line)
30+
self._logger.log(self._level, msg)
3431

3532
def flush(self):
3633
pass

FunctionWorker/python/SessionHelperThread.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import json
1818
import time
19+
import queue
1920
from collections import deque
2021

2122
from DataLayerClient import DataLayerClient
@@ -27,7 +28,7 @@
2728

2829
class SessionHelperThread(threading.Thread):
2930

30-
def __init__(self, helper_params, logger, pubutils, sessutils, queue, datalayer):
31+
def __init__(self, helper_params, logger, pubutils, sessutils, queueservice, datalayer):
3132

3233
self._logger = logger
3334

@@ -37,7 +38,7 @@ def __init__(self, helper_params, logger, pubutils, sessutils, queue, datalayer)
3738

3839
self._session_utils = sessutils
3940

40-
self._queue = queue
41+
self._queue_service = queueservice
4142
self._datalayer = datalayer
4243

4344
self._sandboxid = helper_params["sandboxid"]
@@ -49,7 +50,8 @@ def __init__(self, helper_params, logger, pubutils, sessutils, queue, datalayer)
4950
# need a separate backup data layer client from the publication utils; otherwise, we run into concurrent modification
5051
# problems from Thrift
5152
# locality = -1 means that the writes happen to the local data layer first and then asynchronously to the global data layer
52-
self._backup_data_layer_client = DataLayerClient(locality=-1, for_mfn=True, sid=self._sandboxid, connect=self._datalayer)
53+
# will only initialize if heartbeats are enabled
54+
self._backup_data_layer_client = None
5355

5456
# set up heartbeat parameters
5557
self._heartbeat_enabled = False
@@ -74,12 +76,10 @@ def __init__(self, helper_params, logger, pubutils, sessutils, queue, datalayer)
7476
# we can send regular heartbeats
7577
self._local_poll_timeout = py3utils.ensure_long(10000)
7678

77-
# use a deque to keep the list of messages
78-
# updating the list and retrieving the list would be done by two threads
79-
# this should be safe without lock because of the global interpreter lock in python
80-
self._message_queue = deque()
79+
# use a queue to keep the incoming update messages for blocking and/or blocking get_update_messages() requests
80+
self._message_queue = queue.Queue()
8181

82-
self._local_queue_client = LocalQueueClient(connect=self._queue)
82+
self._local_queue_client = LocalQueueClient(connect=self._queue_service)
8383

8484
self._special_messages = {}
8585
self._special_messages["--stop"] = True
@@ -102,10 +102,15 @@ def _init_heartbeat_parameters(self, heartbeat_params):
102102

103103
if self._heartbeat_method == "function":
104104
if "heartbeat_function" in heartbeat_params:
105+
# enable function related heartbeat
105106
self._heartbeat_function = heartbeat_params["heartbeat_function"]
106107
#self._logger.debug("[SessionHelperThread] New heartbeat function: " + str(self._heartbeat_function))
108+
if self._backup_data_layer_client is None:
109+
self._backup_data_layer_client = DataLayerClient(locality=-1, for_mfn=True, sid=self._sandboxid, connect=self._datalayer)
107110
if self._local_queue_client_heartbeat is None:
108-
self._local_queue_client_heartbeat = LocalQueueClient(connect=self._queue)
111+
self._local_queue_client_heartbeat = LocalQueueClient(connect=self._queue_service)
112+
113+
# disable data layer related heartbeat
109114
if self._data_layer_client_heartbeat is not None:
110115
self._data_layer_client_heartbeat.delete(self._heartbeat_data_layer_key)
111116
self._heartbeat_data_layer_key = None
@@ -118,13 +123,22 @@ def _init_heartbeat_parameters(self, heartbeat_params):
118123
# OR keep a new map for heartbeats of the session functions
119124
# so that the checker can retrieve the keys and their values (e.g., timestamps)
120125
# if a session function misses a heartbeat, the checker function reports to policy handler
126+
127+
# enable data layer related heartbeat
121128
self._heartbeat_data_layer_key = "heartbeat_" + self._session_id + "_" + self._session_function_id
122129
if self._data_layer_client_heartbeat is None:
123130
self._data_layer_client_heartbeat = DataLayerClient(locality=1, for_mfn=True, sid=self._sandboxid, connect=self._datalayer)
131+
132+
# disable function related heartbeat
124133
if self._local_queue_client_heartbeat is not None:
125134
self._local_queue_client_heartbeat.shutdown()
126135
self._local_queue_client_heartbeat = None
127136
self._heartbeat_function = None
137+
if self._backup_data_layer_client is not None:
138+
self._backup_data_layer_client.shutdown()
139+
self._backup_data_layer_client = None
140+
141+
128142
else:
129143
raise MicroFunctionsSessionAPIException("Unsupported heartbeat method for session function.")
130144

@@ -223,19 +237,19 @@ def _process_message(self, lqm):
223237
# _XXX_: we are encoding/decoding the delivered message; should not actually execute this code
224238
# it is here for not envisioned corner case (i.e., let the user code deal with it)
225239
if not is_json:
226-
self._queue_message(msg)
240+
self._store_message(msg)
227241
self._publication_utils.set_metadata(metadata)
228242
else:
229243
# the message is json encoded, but it doesn't guarantee that it is a special message
230244
if "action" in msg and msg["action"] in self._special_messages:
231245
self._handle_special_message(msg)
232246
else:
233-
self._queue_message(msg)
247+
self._store_message(msg)
234248
self._publication_utils.set_metadata(metadata)
235249

236250

237-
def _queue_message(self, msg):
238-
self._message_queue.append(msg)
251+
def _store_message(self, msg):
252+
self._message_queue.put(msg)
239253

240254
def _handle_special_message(self, msg):
241255
action = msg["action"]
@@ -247,17 +261,16 @@ def _handle_special_message(self, msg):
247261
elif action == "--update-heartbeat":
248262
self._init_heartbeat_parameters(msg["heartbeat_parameters"])
249263

250-
def get_messages(self, count=1):
264+
def get_messages(self, count=1, block=False):
251265
messages = []
252266

253-
# this check ensures that we never try to pop() from an empty deque
254-
num_messages = len(self._message_queue)
255-
if num_messages < count:
256-
count = num_messages
257-
258267
for i in range(count):
259-
msg = self._message_queue.popleft()
260-
messages.append(msg)
268+
try:
269+
msg = self._message_queue.get(block=block)
270+
messages.append(msg)
271+
self._message_queue.task_done()
272+
except Exception as exc:
273+
pass
261274

262275
#self._logger.debug("returning messages: " + str(messages))
263276
return messages
@@ -317,19 +330,20 @@ def _cleanup(self):
317330
self._local_queue_client_heartbeat.shutdown()
318331
self._local_queue_client_heartbeat = None
319332

333+
if self._backup_data_layer_client is not None:
334+
self._backup_data_layer_client.shutdown()
335+
self._backup_data_layer_client = None
336+
320337
self._local_queue_client.shutdown()
321338
self._local_queue_client = None
322339

323-
self._backup_data_layer_client.shutdown()
324-
self._backup_data_layer_client = None
325-
326340
def shutdown(self):
327341
self._is_running = False
328342
# remove/unregister the topic here
329343
# if done in _cleanup(), it may be the case that the parent process exits
330344
# and we never get to execute that part in this thread
331345
# need a new lqc, because if using the actual self._local_queue_client,
332346
# it will corrupt the protocol stack, if it is still waiting on new messages
333-
lqc = LocalQueueClient(connect=self._queue)
347+
lqc = LocalQueueClient(connect=self._queue_service)
348+
lqc.removeTopic(self._local_topic_communication)
334349
lqc.shutdown()
335-

FunctionWorker/python/SessionUtils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,9 @@ def send_to_running_function_in_session_with_alias(self, session_function_alias,
476476

477477
self.send_to_running_function_in_session(rgid, message, send_now)
478478

479-
def get_session_update_messages_with_local_queue(self, count=1):
479+
def get_session_update_messages_with_local_queue(self, count=1, block=False):
480480
if self._session_function_id is not None:
481-
messages = self._helper_thread.get_messages(count)
481+
messages = self._helper_thread.get_messages(count=count, block=block)
482482
return messages
483483
return None
484484

0 commit comments

Comments
 (0)