ticket-processing 0.1.0__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.
@@ -0,0 +1,2 @@
1
+ # ticket_processing/__init__.py
2
+ from .ticket_processing import log, request_producer, validator, seat_locker, payment_worker, ticket_issuer, notifier, main
@@ -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,6 @@
1
+ ticket_processing/__init__.py,sha256=ElCF8FKlO1v7sofL-ORTz42vlKQQDRDMtf_oIYINOvw,156
2
+ ticket_processing/ticket_processing.py,sha256=p-2HyWi2WOxf1oHEXgFBCB8nfZGAUUAXKciOabRZ--U,10018
3
+ ticket_processing-0.1.0.dist-info/METADATA,sha256=XUZTtEKXHeTZz5RNAR_UC9Pc4B9gVERXPB9uIcQtDvs,999
4
+ ticket_processing-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ ticket_processing-0.1.0.dist-info/top_level.txt,sha256=W-hRATwDFBlQGmh-ZywDZ9HVLzDWXYSadFncv_pmDCE,18
6
+ ticket_processing-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ticket_processing