dsmq 1.2.3__py3-none-any.whl → 1.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsmq/server.py +106 -100
- dsmq/tests/integration_test.py +0 -1
- {dsmq-1.2.3.dist-info → dsmq-1.2.4.dist-info}/METADATA +1 -1
- {dsmq-1.2.3.dist-info → dsmq-1.2.4.dist-info}/RECORD +6 -6
- {dsmq-1.2.3.dist-info → dsmq-1.2.4.dist-info}/WHEEL +0 -0
- {dsmq-1.2.3.dist-info → dsmq-1.2.4.dist-info}/licenses/LICENSE +0 -0
dsmq/server.py
CHANGED
@@ -5,7 +5,7 @@ import sys
|
|
5
5
|
from threading import Thread
|
6
6
|
import time
|
7
7
|
from websockets.sync.server import serve as ws_serve
|
8
|
-
from websockets.exceptions import ConnectionClosedError
|
8
|
+
from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
|
9
9
|
|
10
10
|
_default_host = "127.0.0.1"
|
11
11
|
_default_port = 30008
|
@@ -70,114 +70,120 @@ def request_handler(websocket):
|
|
70
70
|
last_read_times = {}
|
71
71
|
time_of_last_purge = time.time()
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
msg["
|
73
|
+
try:
|
74
|
+
for msg_text in websocket:
|
75
|
+
msg = json.loads(msg_text)
|
76
|
+
topic = msg["topic"]
|
77
|
+
timestamp = time.time()
|
78
|
+
|
79
|
+
if msg["action"] == "put":
|
80
|
+
msg["timestamp"] = timestamp
|
81
|
+
|
82
|
+
# This block allows for multiple retries if the database
|
83
|
+
# is busy.
|
84
|
+
for i_retry in range(_n_retries):
|
85
|
+
try:
|
86
|
+
cursor.execute(
|
87
|
+
"""
|
88
|
+
INSERT INTO messages (timestamp, topic, message)
|
89
|
+
VALUES (:timestamp, :topic, :message)
|
90
|
+
""",
|
91
|
+
(msg),
|
92
|
+
)
|
93
|
+
sqlite_conn.commit()
|
94
|
+
except sqlite3.OperationalError:
|
95
|
+
wait_time = _first_retry * 2**i_retry
|
96
|
+
time.sleep(wait_time)
|
97
|
+
continue
|
98
|
+
break
|
99
|
+
|
100
|
+
elif msg["action"] == "get":
|
101
|
+
try:
|
102
|
+
last_read_time = last_read_times[topic]
|
103
|
+
except KeyError:
|
104
|
+
last_read_times[topic] = client_creation_time
|
105
|
+
last_read_time = last_read_times[topic]
|
106
|
+
msg["last_read_time"] = last_read_time
|
107
|
+
|
108
|
+
# This block allows for multiple retries if the database
|
109
|
+
# is busy.
|
110
|
+
for i_retry in range(_n_retries):
|
111
|
+
try:
|
112
|
+
cursor.execute(
|
113
|
+
"""
|
114
|
+
SELECT message,
|
115
|
+
timestamp
|
116
|
+
FROM messages,
|
117
|
+
(
|
118
|
+
SELECT MIN(timestamp) AS min_time
|
119
|
+
FROM messages
|
120
|
+
WHERE topic = :topic
|
121
|
+
AND timestamp > :last_read_time
|
122
|
+
) a
|
123
|
+
WHERE topic = :topic
|
124
|
+
AND timestamp = a.min_time
|
125
|
+
""",
|
126
|
+
msg,
|
127
|
+
)
|
128
|
+
except sqlite3.OperationalError:
|
129
|
+
wait_time = _first_retry * 2**i_retry
|
130
|
+
time.sleep(wait_time)
|
131
|
+
continue
|
132
|
+
break
|
80
133
|
|
81
|
-
# This block allows for multiple retries if the database
|
82
|
-
# is busy.
|
83
|
-
for i_retry in range(_n_retries):
|
84
134
|
try:
|
85
|
-
cursor.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
sqlite_conn.commit()
|
93
|
-
except sqlite3.OperationalError:
|
94
|
-
wait_time = _first_retry * 2**i_retry
|
95
|
-
time.sleep(wait_time)
|
96
|
-
continue
|
97
|
-
break
|
135
|
+
result = cursor.fetchall()[0]
|
136
|
+
message = result[0]
|
137
|
+
timestamp = result[1]
|
138
|
+
last_read_times[topic] = timestamp
|
139
|
+
except IndexError:
|
140
|
+
# Handle the case where no results are returned
|
141
|
+
message = ""
|
98
142
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
143
|
+
websocket.send(json.dumps({"message": message}))
|
144
|
+
elif msg["action"] == "shutdown":
|
145
|
+
# Run this from a separate thread to prevent deadlock
|
146
|
+
global dsmq_server
|
147
|
+
|
148
|
+
def shutdown_gracefully(server_to_shutdown):
|
149
|
+
server_to_shutdown.shutdown()
|
150
|
+
|
151
|
+
filenames = os.listdir()
|
152
|
+
for filename in filenames:
|
153
|
+
if filename[: len(_db_name)] == _db_name:
|
154
|
+
try:
|
155
|
+
os.remove(filename)
|
156
|
+
except FileNotFoundError:
|
157
|
+
pass
|
158
|
+
|
159
|
+
Thread(target=shutdown_gracefully, args=(dsmq_server,)).start()
|
160
|
+
break
|
161
|
+
else:
|
162
|
+
raise RuntimeWarning(
|
163
|
+
"dsmq client action must either be 'put', 'get', or 'shutdown'"
|
164
|
+
)
|
165
|
+
|
166
|
+
# Periodically clean out messages from the queue that are
|
167
|
+
# past their sell buy date.
|
168
|
+
# This operation is pretty fast. I clock it at 12 us on my machine.
|
169
|
+
if time.time() - time_of_last_purge > _time_to_live:
|
110
170
|
try:
|
111
171
|
cursor.execute(
|
112
172
|
"""
|
113
|
-
|
114
|
-
timestamp
|
115
|
-
FROM messages,
|
116
|
-
(
|
117
|
-
SELECT MIN(timestamp) AS min_time
|
118
|
-
FROM messages
|
119
|
-
WHERE topic = :topic
|
120
|
-
AND timestamp > :last_read_time
|
121
|
-
) a
|
122
|
-
WHERE topic = :topic
|
123
|
-
AND timestamp = a.min_time
|
173
|
+
DELETE FROM messages
|
174
|
+
WHERE timestamp < :time_threshold
|
124
175
|
""",
|
125
|
-
|
176
|
+
{"time_threshold": time_of_last_purge},
|
126
177
|
)
|
178
|
+
sqlite_conn.commit()
|
179
|
+
time_of_last_purge = time.time()
|
127
180
|
except sqlite3.OperationalError:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
result = cursor.fetchall()[0]
|
135
|
-
message = result[0]
|
136
|
-
timestamp = result[1]
|
137
|
-
last_read_times[topic] = timestamp
|
138
|
-
except IndexError:
|
139
|
-
# Handle the case where no results are returned
|
140
|
-
message = ""
|
141
|
-
|
142
|
-
try:
|
143
|
-
websocket.send(json.dumps({"message": message}))
|
144
|
-
except ConnectionClosedError:
|
145
|
-
pass
|
146
|
-
elif msg["action"] == "shutdown":
|
147
|
-
# Run this from a separate thread to prevent deadlock
|
148
|
-
global dsmq_server
|
149
|
-
|
150
|
-
def shutdown_gracefully(server_to_shutdown):
|
151
|
-
server_to_shutdown.shutdown()
|
152
|
-
|
153
|
-
filenames = os.listdir()
|
154
|
-
for filename in filenames:
|
155
|
-
if filename[: len(_db_name)] == _db_name:
|
156
|
-
try:
|
157
|
-
os.remove(filename)
|
158
|
-
except FileNotFoundError:
|
159
|
-
pass
|
160
|
-
|
161
|
-
Thread(target=shutdown_gracefully, args=(dsmq_server,)).start()
|
162
|
-
break
|
163
|
-
else:
|
164
|
-
raise RuntimeWarning(
|
165
|
-
"dsmq client action must either be 'put', 'get', or 'shutdown'"
|
166
|
-
)
|
167
|
-
|
168
|
-
# Periodically clean out messages from the queue that are
|
169
|
-
# past their sell buy date.
|
170
|
-
# This operation is pretty fast. I clock it at 12 us on my machine.
|
171
|
-
if time.time() - time_of_last_purge > _time_to_live:
|
172
|
-
cursor.execute(
|
173
|
-
"""
|
174
|
-
DELETE FROM messages
|
175
|
-
WHERE timestamp < :time_threshold
|
176
|
-
""",
|
177
|
-
{"time_threshold": time_of_last_purge},
|
178
|
-
)
|
179
|
-
sqlite_conn.commit()
|
180
|
-
time_of_last_purge = time.time()
|
181
|
+
# Database may be locked. Try again next time.
|
182
|
+
pass
|
183
|
+
except (ConnectionClosedError, ConnectionClosedOK):
|
184
|
+
# Something happened on the other end and this handler
|
185
|
+
# is no longer needed.
|
186
|
+
pass
|
181
187
|
|
182
188
|
sqlite_conn.close()
|
183
189
|
|
dsmq/tests/integration_test.py
CHANGED
@@ -3,10 +3,10 @@ dsmq/client.py,sha256=p6irQZOE4b2fpTUwSrEUraPmrJzvT8QSU01ak9qpGCQ,2351
|
|
3
3
|
dsmq/demo.py,sha256=K53cC5kN7K4kNJlPq7c5OTIMHRCKTo9hYX2aIos57rU,542
|
4
4
|
dsmq/example_get_client.py,sha256=PvAsDGEAH1kVBifLVg2rx8ZxnAZmvzVCvZq13VgpLds,301
|
5
5
|
dsmq/example_put_client.py,sha256=QxDc3i7KAjjhpwxRRpI0Ke5KTNSPuBf9kkcGyTvUEaw,353
|
6
|
-
dsmq/server.py,sha256=
|
6
|
+
dsmq/server.py,sha256=5c4mHWz6yslSxpOwrZ3vOCMcx_cm8zMF8iJNnQF8ft0,7249
|
7
7
|
dsmq/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
dsmq/tests/integration_test.py,sha256=
|
9
|
-
dsmq-1.2.
|
10
|
-
dsmq-1.2.
|
11
|
-
dsmq-1.2.
|
12
|
-
dsmq-1.2.
|
8
|
+
dsmq/tests/integration_test.py,sha256=dLsQGCmpXv4zRb93TriccH7TbUyD9MHcLckAQqfDOK4,5980
|
9
|
+
dsmq-1.2.4.dist-info/METADATA,sha256=CswX8bQph2pF-k-VMCLAPqaho3Mhny4xDLJ70xwWa9I,4730
|
10
|
+
dsmq-1.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
11
|
+
dsmq-1.2.4.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
|
12
|
+
dsmq-1.2.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|