Skip to content

Commit 5779003

Browse files
authored
Merge pull request #29 from dev-dull/28-clean-shutdown
#28 - implement a safe shutdown endpoint
2 parents bcf7834 + c092f3c commit 5779003

3 files changed

Lines changed: 53 additions & 20 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,11 @@ Note that unregistering an ID is destructive and all data for that `id` will be
167167
user@shell> curl -Ss -X DELETE -H 'X-Api-Key: your-api-key-here' 'http://127.0.0.1:5000/unregister?id=testing'
168168
Success
169169
```
170+
171+
### Cleanly shutting down PyXIE
172+
If PyXIE is killed while in the middle of persisting data to disk, it will likely result in a corrupted file. To ensure that your data is complete and well formed, use one of your defined API keys ot send a `POST` request to the `/shutdown` endpoint like in the following example. This will tell PyXIE to write all data to disk and exit.
173+
174+
```bash
175+
user@shell> curl -Ss -X POST -H 'X-Api-Key: your-api-key-here' 'http://127.0.0.1:5000/shutdown'
176+
curl: (52) Empty reply from server
177+
```

constfig.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def __init__(self):
2020
self.load_config()
2121

2222
def set_constants(self):
23+
# `C` (the instance of `_C()` set below) is only created once even though it is imported in `ddb` and `pyxie`.
24+
# Probably not a good idea for _SHUTDOWN to depend on that behavior for the long term. Only used in `pyxie` currently.
25+
self._SHUTDOWN = False # Toggled to true to cleanly stop the service and ensure data is persisted to disk.
26+
2327
# Constant values
2428
self.APP_NAME = "pyxie"
2529
self.LOG = logging.getLogger(self.APP_NAME)

pyxie.py

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import os
12
from ddb import DDB
2-
from time import time
33
from constfig import C
4-
from flask import Flask, Response, request
4+
from flask import Flask, Response, request, abort
55

66

77
pyxie = Flask(C.APP_NAME)
@@ -13,39 +13,54 @@ def _validate_api_key():
1313
return api_key in C.API_KEYS
1414

1515

16+
@pyxie.before_request
17+
def check_if_shutting_down():
18+
# Since we're currently single threaded, we shouldn't really need this check. This is here
19+
# for any weird edge cases with flask.
20+
if C._SHUTDOWN:
21+
abort(503, description="Service is shutting down. Please try again later.")
22+
23+
24+
@pyxie.before_request
25+
def validate_api_key():
26+
if request.path == "/" or _validate_api_key():
27+
return
28+
return "Unauthorized", 401
29+
30+
31+
@pyxie.after_request
32+
def shutdown(response):
33+
if C._SHUTDOWN:
34+
_data.dump()
35+
os._exit(0)
36+
return response
37+
38+
1639
@pyxie.route("/register", methods=[C.HTTP_METHOD_POST])
1740
def register():
18-
if _validate_api_key():
19-
_data.register()
20-
return "Success", 201
21-
return "Unauthorized", 401
41+
_data.register()
42+
return "Success", 201
2243

2344

2445
@pyxie.route("/unregister", methods=[C.HTTP_METHOD_DELETE])
2546
def unregister():
26-
if _validate_api_key():
27-
_data.unregister()
28-
return "Success", 204
29-
return "Unauthorized", 401
47+
_data.unregister()
48+
return "Success", 204
3049

3150

3251
@pyxie.route("/stats", methods=[C.HTTP_METHOD_GET])
3352
def stats():
34-
if _validate_api_key():
35-
stats = {}
36-
for attr in dir(_data):
37-
if isinstance(getattr(type(_data), attr, None), property):
38-
stats[attr] = getattr(_data, attr)
39-
return stats, 200
40-
return "Unauthorized", 401
53+
stats = {}
54+
for attr in dir(_data):
55+
if isinstance(getattr(type(_data), attr, None), property):
56+
stats[attr] = getattr(_data, attr)
57+
return stats, 200
4158

4259

4360
@pyxie.route("/metrics", methods=[C.HTTP_METHOD_GET])
4461
def metrics():
4562
# [ TODO - Issue #7] - Export prometheus formatted metrics
46-
if _validate_api_key():
47-
return "Metrics", 501 # Not Implemented
48-
return "Unauthorized", 401
63+
return "Metrics", 501 # Not Implemented
4964

5065

5166
@pyxie.route("/", methods=[C.HTTP_METHOD_GET])
@@ -58,6 +73,12 @@ def root():
5873
return Response(C.ONE_BY_ONE, mimetype=C.HTTP_MIME_TYPE_PNG)
5974

6075

76+
@pyxie.route("/shutdown", methods=[C.HTTP_METHOD_POST])
77+
def shutdown():
78+
C._SHUTDOWN = True
79+
return "Service is now offline. Data will be saved.", 200
80+
81+
6182
def main():
6283
pyxie.run(host=C.LISTEN_IP, port=C.LISTEN_PORT)
6384

0 commit comments

Comments
 (0)