ticket-processing 0.1.0__tar.gz
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.
- ticket_processing-0.1.0/PKG-INFO +37 -0
- ticket_processing-0.1.0/README.md +16 -0
- ticket_processing-0.1.0/pyproject.toml +3 -0
- ticket_processing-0.1.0/setup.cfg +4 -0
- ticket_processing-0.1.0/setup.py +19 -0
- ticket_processing-0.1.0/ticket_processing/__init__.py +2 -0
- ticket_processing-0.1.0/ticket_processing/ticket_processing.py +310 -0
- ticket_processing-0.1.0/ticket_processing.egg-info/PKG-INFO +37 -0
- ticket_processing-0.1.0/ticket_processing.egg-info/SOURCES.txt +9 -0
- ticket_processing-0.1.0/ticket_processing.egg-info/dependency_links.txt +1 -0
- ticket_processing-0.1.0/ticket_processing.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ticket-processing
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple application for purchasing and processing tickets
|
|
5
|
+
Home-page: https://github.com/zZ72ehsan74Zz/ticket-processing
|
|
6
|
+
Author: ehsan bahmanipor
|
|
7
|
+
Author-email: eh.bahmanipor@gamil.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# Ticket Processing
|
|
23
|
+
A simple application for purchasing and processing tickets.
|
|
24
|
+
The features of this program are as follows:
|
|
25
|
+
|
|
26
|
+
1) Request producer
|
|
27
|
+
2) Validator
|
|
28
|
+
3) Seat locker
|
|
29
|
+
4) Payment worker
|
|
30
|
+
5) Ticket issuer
|
|
31
|
+
6) Notifier
|
|
32
|
+
|
|
33
|
+
And it is implemented based on multithreading.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
```bash
|
|
37
|
+
pip install ticket-processing
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Ticket Processing
|
|
2
|
+
A simple application for purchasing and processing tickets.
|
|
3
|
+
The features of this program are as follows:
|
|
4
|
+
|
|
5
|
+
1) Request producer
|
|
6
|
+
2) Validator
|
|
7
|
+
3) Seat locker
|
|
8
|
+
4) Payment worker
|
|
9
|
+
5) Ticket issuer
|
|
10
|
+
6) Notifier
|
|
11
|
+
|
|
12
|
+
And it is implemented based on multithreading.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
```bash
|
|
16
|
+
pip install ticket-processing
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="ticket-processing",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
author="ehsan bahmanipor",
|
|
7
|
+
author_email="eh.bahmanipor@gamil.com",
|
|
8
|
+
description="A simple application for purchasing and processing tickets",
|
|
9
|
+
long_description=open('README.md').read(),
|
|
10
|
+
long_description_content_type='text/markdown',
|
|
11
|
+
url="https://github.com/zZ72ehsan74Zz/ticket-processing",
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
classifiers=[
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
],
|
|
18
|
+
python_requires='>=3.6',
|
|
19
|
+
)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
from threading import BrokenBarrierError
|
|
4
|
+
|
|
5
|
+
requests_queue, valid_queue, locked_queue, paid_queue, notify_queue, threads = [], [], [], [], [], []
|
|
6
|
+
requests_created, valid_ok, valid_fail, locked_ok, locked_fail, paid_ok, paid_fail, payment_workers_done, issued_count, notified_count, canceled_notifications = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
7
|
+
producer_done, validator_done, locker_done, payment_done, issuer_done = False, False, False, False, False
|
|
8
|
+
|
|
9
|
+
start_event = threading.Event()
|
|
10
|
+
shutdown_event = threading.Event()
|
|
11
|
+
lock = threading.Lock()
|
|
12
|
+
cond = threading.Condition(lock)
|
|
13
|
+
payment_slots = threading.Semaphore(3)
|
|
14
|
+
seat_reservation = threading.BoundedSemaphore(5)
|
|
15
|
+
barrier = threading.Barrier(4)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def log(msg: str):
|
|
19
|
+
now = time.strftime("%H:%M:%S")
|
|
20
|
+
name = threading.current_thread().name
|
|
21
|
+
print(f"[{now}] ({name}) {msg}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def request_producer(total_requests=20):
|
|
25
|
+
global requests_created, producer_done
|
|
26
|
+
|
|
27
|
+
start_event.wait()
|
|
28
|
+
log("Started")
|
|
29
|
+
|
|
30
|
+
with cond:
|
|
31
|
+
for request_id in range(1, total_requests + 1):
|
|
32
|
+
requests_queue.append({
|
|
33
|
+
"request_id": request_id,
|
|
34
|
+
"user_id": (request_id % 7) + 1,
|
|
35
|
+
"seat_count": 1 if request_id % 3 else 2
|
|
36
|
+
})
|
|
37
|
+
requests_created += 1
|
|
38
|
+
log(f"Created request: {request_id} | requests_queue: {len(requests_queue)}")
|
|
39
|
+
producer_done = True
|
|
40
|
+
cond.notify_all()
|
|
41
|
+
|
|
42
|
+
log("Finished")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validator():
|
|
46
|
+
global valid_ok, valid_fail, validator_done
|
|
47
|
+
|
|
48
|
+
start_event.wait()
|
|
49
|
+
log("Started")
|
|
50
|
+
|
|
51
|
+
while True:
|
|
52
|
+
with cond:
|
|
53
|
+
while not requests_queue and not producer_done and not shutdown_event.is_set():
|
|
54
|
+
log("Waiting for requests...")
|
|
55
|
+
cond.wait(timeout=1)
|
|
56
|
+
|
|
57
|
+
if shutdown_event.is_set():
|
|
58
|
+
log("Shutdown detected -> exiting")
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
if not requests_queue and producer_done:
|
|
62
|
+
validator_done = True
|
|
63
|
+
cond.notify_all()
|
|
64
|
+
log("No more requests & producer_done=True -> validator_done=True")
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
req = requests_queue.pop(0)
|
|
68
|
+
log(f"Picked request={req} from requests_queue | left={len(requests_queue)}")
|
|
69
|
+
|
|
70
|
+
time.sleep(.05)
|
|
71
|
+
|
|
72
|
+
user_id = req["user_id"]
|
|
73
|
+
seat_count = req["seat_count"]
|
|
74
|
+
|
|
75
|
+
with cond:
|
|
76
|
+
if (user_id % 2 != 0) or (seat_count < 1 or seat_count > 2):
|
|
77
|
+
valid_fail += 1
|
|
78
|
+
log(f"Validation FAILED request={req}")
|
|
79
|
+
else:
|
|
80
|
+
valid_ok += 1
|
|
81
|
+
valid_queue.append(req)
|
|
82
|
+
log(f"Validated Ok request={req} and valid_queue={len(valid_queue)}")
|
|
83
|
+
cond.notify_all()
|
|
84
|
+
|
|
85
|
+
log("Finished")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def seat_locker():
|
|
89
|
+
global locked_ok, locked_fail, locker_done
|
|
90
|
+
|
|
91
|
+
start_event.wait()
|
|
92
|
+
log("Started")
|
|
93
|
+
|
|
94
|
+
while True:
|
|
95
|
+
with cond:
|
|
96
|
+
while not valid_queue and not validator_done and not shutdown_event.is_set():
|
|
97
|
+
log("Waiting for valid requests...")
|
|
98
|
+
cond.wait(timeout=1)
|
|
99
|
+
|
|
100
|
+
if shutdown_event.is_set():
|
|
101
|
+
log("Shutdown detected -> exiting")
|
|
102
|
+
break
|
|
103
|
+
|
|
104
|
+
if not valid_queue and validator_done:
|
|
105
|
+
locker_done = True
|
|
106
|
+
cond.notify_all()
|
|
107
|
+
log("No more valid requests & validator_done=True -> locker_done=True")
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
req = valid_queue.pop(0)
|
|
111
|
+
log(f"Picked req={req} from valid queue | left={len(valid_queue)}")
|
|
112
|
+
|
|
113
|
+
acquired = seat_reservation.acquire(timeout=2)
|
|
114
|
+
with cond:
|
|
115
|
+
if acquired:
|
|
116
|
+
locked_ok += 1
|
|
117
|
+
locked_queue.append(req)
|
|
118
|
+
log(f"Seat LOCKED req={req} | locked_queue={len(locked_queue)}")
|
|
119
|
+
else:
|
|
120
|
+
locked_fail += 1
|
|
121
|
+
log(f"Seat LOCK FAILED timeout req={req}")
|
|
122
|
+
|
|
123
|
+
cond.notify_all()
|
|
124
|
+
|
|
125
|
+
log("Finished")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def payment_worker(payment_workers_total=1):
|
|
129
|
+
global paid_ok, paid_fail, payment_done, payment_workers_done
|
|
130
|
+
|
|
131
|
+
start_event.wait()
|
|
132
|
+
log("Started")
|
|
133
|
+
|
|
134
|
+
while True:
|
|
135
|
+
with cond:
|
|
136
|
+
while not locked_queue and not locker_done and not shutdown_event.is_set():
|
|
137
|
+
log("Waiting for locked requests...")
|
|
138
|
+
cond.wait(timeout=1)
|
|
139
|
+
|
|
140
|
+
if shutdown_event.is_set():
|
|
141
|
+
log("Shutdown detected -> exiting")
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
if not locked_queue and locker_done:
|
|
145
|
+
payment_workers_done += 1
|
|
146
|
+
if payment_workers_done == payment_workers_total:
|
|
147
|
+
payment_done = True
|
|
148
|
+
cond.notify_all()
|
|
149
|
+
log("All payment workers done -> payment_done=True")
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
req = locked_queue.pop(0)
|
|
153
|
+
log(f"Picked req={req} from locked_queue | left={len(locked_queue)}")
|
|
154
|
+
|
|
155
|
+
acquired = payment_slots.acquire(timeout=2)
|
|
156
|
+
if not acquired:
|
|
157
|
+
with cond:
|
|
158
|
+
paid_fail += 1
|
|
159
|
+
seat_reservation.release()
|
|
160
|
+
log(f"Payment slot timeout -> FAILED req={req}")
|
|
161
|
+
cond.notify_all()
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
is_paid = req["request_id"] % 5 != 0
|
|
166
|
+
time.sleep(.08)
|
|
167
|
+
|
|
168
|
+
with cond:
|
|
169
|
+
if is_paid:
|
|
170
|
+
paid_ok += 1
|
|
171
|
+
paid_queue.append(req)
|
|
172
|
+
log(f"Payment OK req={req} | paid_queue={len(paid_queue)}")
|
|
173
|
+
else:
|
|
174
|
+
paid_fail += 1
|
|
175
|
+
seat_reservation.release()
|
|
176
|
+
log(f"Payment FAILED req={req} (rule)")
|
|
177
|
+
cond.notify_all()
|
|
178
|
+
finally:
|
|
179
|
+
payment_slots.release()
|
|
180
|
+
|
|
181
|
+
log("Finished")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def ticket_issuer():
|
|
185
|
+
global issued_count, issuer_done
|
|
186
|
+
|
|
187
|
+
start_event.wait()
|
|
188
|
+
log("Started")
|
|
189
|
+
|
|
190
|
+
while True:
|
|
191
|
+
with cond:
|
|
192
|
+
while not paid_queue and not payment_done and not shutdown_event.is_set():
|
|
193
|
+
log("Waiting for paid queue...")
|
|
194
|
+
cond.wait(timeout=1)
|
|
195
|
+
|
|
196
|
+
if shutdown_event.is_set():
|
|
197
|
+
log("Shutdown detected -> exiting")
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
if not paid_queue and payment_done:
|
|
201
|
+
issuer_done = True
|
|
202
|
+
cond.notify_all()
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
req = paid_queue.pop(0)
|
|
206
|
+
log(f"Picked req={req} from paid queue | left={len(paid_queue)}")
|
|
207
|
+
|
|
208
|
+
time.sleep(.05)
|
|
209
|
+
|
|
210
|
+
with cond:
|
|
211
|
+
issued_count += 1
|
|
212
|
+
notify_queue.append(req)
|
|
213
|
+
cond.notify_all()
|
|
214
|
+
|
|
215
|
+
log("Finished")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def notifier():
|
|
219
|
+
global notified_count, canceled_notifications
|
|
220
|
+
|
|
221
|
+
start_event.wait()
|
|
222
|
+
log("Started")
|
|
223
|
+
|
|
224
|
+
while True:
|
|
225
|
+
with cond:
|
|
226
|
+
while not notify_queue and not issuer_done and not shutdown_event.is_set():
|
|
227
|
+
log("Waiting for issuer...")
|
|
228
|
+
cond.wait(timeout=1)
|
|
229
|
+
|
|
230
|
+
if shutdown_event.is_set() and not notify_queue:
|
|
231
|
+
log("Shutdown + no notify -> exiting")
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
if issuer_done and not notify_queue:
|
|
235
|
+
log("Issuer done + notify_queue empty -> exiting")
|
|
236
|
+
break
|
|
237
|
+
|
|
238
|
+
if not notify_queue:
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
req = notify_queue.pop(0)
|
|
242
|
+
log(f"Picked req={req} from notify queue | left={len(notify_queue)}")
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
idx = barrier.wait(timeout=2)
|
|
246
|
+
with cond:
|
|
247
|
+
notified_count += 1
|
|
248
|
+
log(f"Barrier passed (idx={idx}) -> req={req}")
|
|
249
|
+
cond.notify_all()
|
|
250
|
+
except BrokenBarrierError:
|
|
251
|
+
with cond:
|
|
252
|
+
canceled_notifications += 1
|
|
253
|
+
log(f"Barrier broken -> canceled req={req}")
|
|
254
|
+
cond.notify_all()
|
|
255
|
+
|
|
256
|
+
log("Finished")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def main():
|
|
260
|
+
threads.append(threading.Thread(target=request_producer, name="Request_producer-1"))
|
|
261
|
+
threads.append(threading.Thread(target=validator, name="Validator-1"))
|
|
262
|
+
threads.append(threading.Thread(target=seat_locker, name="Seat_locker-1"))
|
|
263
|
+
threads.append(threading.Thread(target=payment_worker, name="Payment_worker-1"))
|
|
264
|
+
threads.append(threading.Thread(target=ticket_issuer, name="Ticket_issuer-1"))
|
|
265
|
+
|
|
266
|
+
for i in range(1, 5):
|
|
267
|
+
n = threading.Thread(target=notifier, name=f"Notifier-{i}")
|
|
268
|
+
threads.append(n)
|
|
269
|
+
|
|
270
|
+
for t in threads:
|
|
271
|
+
t.start()
|
|
272
|
+
|
|
273
|
+
log("All thread started. Releasing start_event...")
|
|
274
|
+
start_event.set()
|
|
275
|
+
|
|
276
|
+
deadline = time.monotonic() + 10
|
|
277
|
+
for t in threads:
|
|
278
|
+
remain = deadline - time.monotonic()
|
|
279
|
+
if remain > 0:
|
|
280
|
+
t.join(timeout=remain)
|
|
281
|
+
|
|
282
|
+
alive = [t for t in threads if t.is_alive()]
|
|
283
|
+
if alive:
|
|
284
|
+
log(f"Some threads still alive ({len(alive)}). Triggering shutdown...")
|
|
285
|
+
shutdown_event.set()
|
|
286
|
+
with cond:
|
|
287
|
+
cond.notify_all()
|
|
288
|
+
try:
|
|
289
|
+
barrier.abort()
|
|
290
|
+
log("Barrier aborted")
|
|
291
|
+
except RuntimeError:
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
for t in alive:
|
|
295
|
+
t.join(timeout=3)
|
|
296
|
+
|
|
297
|
+
with cond:
|
|
298
|
+
log("=== FINAL REPORT ===")
|
|
299
|
+
log(f"request created={requests_created}")
|
|
300
|
+
log(f"valid_ok={valid_ok}, valid_fail={valid_fail}")
|
|
301
|
+
log(f"locked_ok={locked_ok}, locked_fail={locked_fail}")
|
|
302
|
+
log(f"paid_ok={paid_ok}, paid_fail={paid_fail}")
|
|
303
|
+
log(f"issued_count={issued_count}")
|
|
304
|
+
log(f"notified count={notified_count}, canceled_notifications={canceled_notifications}")
|
|
305
|
+
log(f"remaining queues -> req={len(requests_queue)}, valid={len(valid_queue)}, locked={len(locked_queue)}, paid={len(paid_queue)}, notify={len(notify_queue)}")
|
|
306
|
+
log("=== END ===")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ticket-processing
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple application for purchasing and processing tickets
|
|
5
|
+
Home-page: https://github.com/zZ72ehsan74Zz/ticket-processing
|
|
6
|
+
Author: ehsan bahmanipor
|
|
7
|
+
Author-email: eh.bahmanipor@gamil.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Dynamic: author
|
|
14
|
+
Dynamic: author-email
|
|
15
|
+
Dynamic: classifier
|
|
16
|
+
Dynamic: description
|
|
17
|
+
Dynamic: description-content-type
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
Dynamic: summary
|
|
21
|
+
|
|
22
|
+
# Ticket Processing
|
|
23
|
+
A simple application for purchasing and processing tickets.
|
|
24
|
+
The features of this program are as follows:
|
|
25
|
+
|
|
26
|
+
1) Request producer
|
|
27
|
+
2) Validator
|
|
28
|
+
3) Seat locker
|
|
29
|
+
4) Payment worker
|
|
30
|
+
5) Ticket issuer
|
|
31
|
+
6) Notifier
|
|
32
|
+
|
|
33
|
+
And it is implemented based on multithreading.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
```bash
|
|
37
|
+
pip install ticket-processing
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
ticket_processing/__init__.py
|
|
5
|
+
ticket_processing/ticket_processing.py
|
|
6
|
+
ticket_processing.egg-info/PKG-INFO
|
|
7
|
+
ticket_processing.egg-info/SOURCES.txt
|
|
8
|
+
ticket_processing.egg-info/dependency_links.txt
|
|
9
|
+
ticket_processing.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ticket_processing
|