11import sched
22import time
3+ import ctypes
34import random
45import requests
5- from threading import Thread
6+ import threading
67from . import constants
78from threading import Semaphore
89from .prepare_reese84token import getReese84Token
910from storage .storage import *
1011
12+
1113class Reese84TokenUpdating ():
1214 def __init__ (self ):
1315 self .is_running = False
1416 self .reese84_token = {}
1517 self .token_semaphore = Semaphore (0 )
1618 self .scheduler = sched .scheduler (time .time , time .sleep )
17-
19+
1820 def initialize_reese84_token (self ):
1921 """
2022 This method should not be called directly.
2123 """
2224 self .reese84_token = getReese84Token ()
23- self .token_semaphore .release () # produce a new token
25+ self .token_semaphore .release () # produce a new token
2426 self .scheduler .enter (self .reese84_token ['renewInSec' ] -
2527 constants .TOKEN_RENEW_SEC_OFFSET , constants .TOKEN_RENEW_PRIORITY , self .renew_reese84_token )
2628
@@ -29,24 +31,26 @@ def renew_reese84_token(self):
2931 This method should not be called directly.
3032 """
3133 print ("renewing token" )
32- self .token_semaphore .acquire () # invalidate a token
34+ self .token_semaphore .acquire () # invalidate a token
3335 self .reese84_token = getReese84Token ()
3436 self .token_semaphore .release ()
3537 self .scheduler .enter (self .reese84_token ['renewInSec' ] -
3638 constants .TOKEN_RENEW_SEC_OFFSET , constants .TOKEN_RENEW_PRIORITY , self .renew_reese84_token )
37-
39+
3840 def start (self ):
3941 # if the scheduler is already started - do nothing
40- if self .is_running : return
42+ if self .is_running :
43+ return
4144 self .is_running = True
4245 self .initialize_reese84_token ()
4346 self .scheduler .run ()
44-
4547
4648
47- class TicketScraping ():
49+ class TicketScraping (threading . Thread ):
4850 def __init__ (self , token_generator : Reese84TokenUpdating , event_id , subscribe_id , num_seats = 2 , price_range = (0 , 200 )):
51+ threading .Thread .__init__ (self )
4952 self .is_running = False
53+ self .is_stopping = False
5054 self .event_id = event_id
5155 self .subscribe_id = subscribe_id
5256 self .num_seats = num_seats
@@ -56,6 +60,19 @@ def __init__(self, token_generator: Reese84TokenUpdating, event_id, subscribe_id
5660 self .initialDelay = random .randint (
5761 1 , constants .TICKET_SCRAPING_INTERVAL )
5862
63+ def flag_for_termination (self ):
64+ # cancel all scheduled jobs
65+ list (map (self .scheduler .cancel , self .scheduler .queue ))
66+ # raise exception to terminate the thread
67+ thread_id = self .get_id ()
68+ res = ctypes .pythonapi .PyThreadState_SetAsyncExc (thread_id ,
69+ ctypes .py_object (SystemExit ))
70+ print (
71+ f"Ticket scraping with subscription id={ self .subscribe_id } marked for termination." )
72+ if res > 1 :
73+ ctypes .pythonapi .PyThreadState_SetAsyncExc (thread_id , 0 )
74+ print (f'Failed to terminate the thread with id={ thread_id } ' )
75+
5976 def ticket_scraping (self ):
6077 if self .token_gen .token_semaphore ._value <= 0 :
6178 # retry after a delay
@@ -71,34 +88,89 @@ def ticket_scraping(self):
7188 # print(res.json())
7289 print ("Got the ticket info from TM. /" , res .status_code )
7390 self .scheduler .enter (constants .TICKET_SCRAPING_INTERVAL ,
74- constants .TICKET_SCRAPING_PRIORITY , self .ticket_scraping )
91+ constants .TICKET_SCRAPING_PRIORITY , self .ticket_scraping )
7592
76- def start (self ):
77- # if the scheduler is already started - do nothing
78- if self .is_running :
79- return
80- self .is_running = True
81- self .ticket_scraping ()
82- # randomize start time to scatter out event of API fetching
83- time .sleep (self .initialDelay )
84- self .scheduler .run ()
93+ def get_id (self ):
94+ # returns id of the respective thread
95+ if hasattr (self , '_thread_id' ):
96+ return self ._thread_id
97+ for id , thread in threading ._active .items ():
98+ if thread is self :
99+ return id
100+
101+ def run (self ):
102+ try :
103+ # if the scheduler is already started - do nothing
104+ if self .is_running :
105+ return
106+ self .is_running = True
107+ self .is_stopping = False
108+ self .ticket_scraping ()
109+ # randomize start time to scatter out event of API fetching
110+ time .sleep (self .initialDelay )
111+ self .scheduler .run ()
112+ finally :
113+ print (
114+ f"Ticket scraping with subscription id={ self .subscribe_id } has been terminated." )
85115
86116
87117def start ():
88118 # reese84 token renewing thread
89119 reese_token_gen = Reese84TokenUpdating ()
90- serverThread_reese = Thread (target = reese_token_gen .start )
120+ serverThread_reese = threading . Thread (target = reese_token_gen .start )
91121 serverThread_reese .start ()
92122
93123 # ticket scraping threads
94- scraping_list = []
95- events = find_many (constants .DATABASE ["EVENTS" ], {})
96-
124+ scraping_list = dict ()
125+ events = find_many (constants .DATABASE ["EVENTS" ], {
126+ '$or' : [{ 'markPaused' : { '$exists' : False }}, { 'markPaused' : False }]})
97127 for evt in events :
98- ticket_scraping = TicketScraping (reese_token_gen , evt ["tm_event_id" ], evt ["_id" ], evt ["ticket_num" ], evt ["price_range" ])
128+ ticket_scraping = TicketScraping (
129+ reese_token_gen , evt ["tm_event_id" ], evt ["_id" ], evt ["ticket_num" ], evt ["price_range" ])
99130 print (ticket_scraping .initialDelay , "s" )
100- serverThread_ticket_scraping = Thread (target = ticket_scraping .start )
101- scraping_list .append (serverThread_ticket_scraping )
102-
103- for scraping_thread in scraping_list :
131+ scraping_list [ticket_scraping .subscribe_id ] = ticket_scraping
132+ for scraping_thread in scraping_list .values ():
104133 scraping_thread .start ()
134+
135+ # listen for changes in ticket scraping subscriptions
136+ while (True ):
137+ with watch (constants .DATABASE ["EVENTS" ], pipeline = [{'$match' : {'operationType' : {'$in' : ['delete' , 'insert' , 'replace' , 'update' ]}}}], full_document = 'updateLookup' ) as stream :
138+ for change in stream :
139+ if change ['operationType' ] == "delete" :
140+ # stop the thread
141+ doc_id = change ['documentKey' ]['_id' ]
142+ if doc_id in scraping_list :
143+ scraping_obj = scraping_list [doc_id ]
144+ scraping_obj .flag_for_termination ()
145+ del scraping_list [doc_id ]
146+ elif change ['operationType' ] == "insert" :
147+ # spawn a thread to do scraping operations
148+ full_doc = change ['fullDocument' ]
149+ ticket_scraping = TicketScraping (
150+ reese_token_gen , full_doc ["tm_event_id" ], full_doc ["_id" ], full_doc ["ticket_num" ], full_doc ["price_range" ])
151+ print (ticket_scraping .initialDelay , "s" )
152+ scraping_list [ticket_scraping .subscribe_id ] = ticket_scraping
153+ ticket_scraping .start ()
154+ else :
155+ # replace or update - pause or resume ticket scraping
156+ full_doc = change ['fullDocument' ]
157+ doc_id = full_doc ['_id' ]
158+ if not 'markPaused' in full_doc :
159+ print ("'markPaused' flag is unset, skip processing." )
160+ break
161+ if full_doc ['markPaused' ] == True :
162+ # pause scraping
163+ if doc_id in scraping_list :
164+ scraping_obj = scraping_list [doc_id ]
165+ scraping_obj .flag_for_termination ()
166+ del scraping_list [doc_id ]
167+ else :
168+ # resume scraping if currently paused
169+ if doc_id not in scraping_list :
170+ ticket_scraping = TicketScraping (
171+ reese_token_gen , full_doc ["tm_event_id" ], full_doc ["_id" ], full_doc ["ticket_num" ], full_doc ["price_range" ])
172+ print (ticket_scraping .initialDelay , "s" )
173+ scraping_list [ticket_scraping .subscribe_id ] = ticket_scraping
174+ ticket_scraping .start ()
175+ # display current number of ticket scraping
176+ print (f"{ len (scraping_list )} ticket scraping threads now." )
0 commit comments