Interrupt an earlier timeout event in Simpy - simpy

I would like to create a system with servers which need time to set-up before being ready to serve. A server is set up whenever there is a customer arriving to the queue, and the the earlier coming customer will seize the server which is ON earlier, like below.
Customer 1 arrives and requests a server.
Server 1 is SETUP in t1 secs.
Customer 2 arrives and requests a server.
Server 2 is SETUP in t2 secs.
Server 2 is ON.
Customer 1 occupies Server 2.
This process has been successfully simulated thanks to the great answer here:
Simulating a system of resource with set-up/switch-on times using Simpy
I would like to add another policy to the system. When a customer leaves the system, he checks if there is any other customer who is waiting to be served. If so, he keeps the server remaining ON, otherwise, he turns off the server immediatelty.
Customer 1 completes the service and leaves the system.
Server 2 remains ON.
Customer 2 sees that Server 2 is ON before Server 1, and sees that no one else is waiting, so he turns off Server 1.
Customer 2 occupies Server 2.
Server 1 (still in SETUP mode) is turned off.
So, 7),8),9),10) happen at the same time, and the occurence of Event 7) triggers the Interuption of the ealier Event 2) in 10).
Is it possible to manage such kind of Interruption in the Server_Management() class?
"""
Simulation of a dynamic server pool
Server pool starts empty and servers are added as needed, but there is a delay
simulating start up time before the server is available to fulfill a resource request
Programmer: Matt
Wrote original version
Programmer: Michael R. Gibbs
Added server check for dynamicaly adding servers
Fixed return of resorces to pool
"""
import simpy
LAM = 8 #arival rate of jobs
MU = 2 #service rate
ALPHA = 12 #set up rate
NUM_SERVERS = 3
MAX_NUM_JOB = 10000000000
UNTIL = 5
num_current_jobs = 0 #global variable which record the number of jobs present in the system
def generate_interarrival():
return np.random.exponential(1/LAM)
def generate_service():
return np.random.exponential(1/MU)
def switch_on():
return np.random.exponential(1/ALPHA)
class Generate_Job():
def arriving_job(env, servers):
global num_current_jobs, num_server_on, leaving_time_list
for i in range(MAX_NUM_JOB):
job = Job(name="Job%01d" % (i))
yield env.timeout(generate_interarrival())
print('{0:.5f}'.format(env.now), job.name, "arrives")
num_current_jobs +=1
env.process(job.handling(env,servers))
class Server_Management():
def check_servers_arriving(env, servers):
global num_current_jobs
"""
Checks the server pool to see if the pool has any avalable servers
if not then add a server, (there will be a delay before added server becomes available)
Call this without a yield so it does not block if a server is added
"""
print('{0:.5f}'.format(env.now), "checking #OFF servers:",max(0,NUM_SERVERS-num_current_jobs+1))
if num_current_jobs <= NUM_SERVERS:
# will need another server
switch_on_time = switch_on()
print('{0:.5f}'.format(env.now), "adding a server at " + '{0:.5f}'.format(env.now + switch_on_time) + " --")
yield env.timeout(switch_on_time) #switch on time
yield servers.put(1)
print('{0:.5f}'.format(env.now), "added a server--")
def check_servers_leaving(env, servers):
global num_current_jobs
"""
Checks the queue to see if there is any customer is waiting
"""
print('{0:.5f}'.format(env.now), "checking queue length:",max(0,num_current_jobs-NUM_SERVERS))
if num_current_jobs >= NUM_SERVERS: #if there is any waiting customer
yield servers.put(1) #Keep the computer remain ON
print('{0:.5f}'.format(env.now), "computer remains ON--")
class Room: # A room containing servers (resource)
def __init__(self, env):
self.computer = simpy.Container(env, capacity = NUM_SERVERS, init = 0)
class Job(object):
def __init__(self,name):
self.name = name
def handling(self, env, servers):
global num_current_jobs, num_server_on, job_list
# added a check to see if a resource pool needs another server.
env.process(Server_Management.check_servers_arriving(env,servers.computer))
print('{0:.5f}'.format(env.now), self.name, "requesting a server--")
with servers.computer.get(1) as req:
yield req
print('{0:.5f}'.format(env.now), self.name, "occupies a server--")
yield env.timeout(generate_service()) #service time
print('{0:.5f}'.format(env.now), self.name, "leaves")
num_current_jobs -= 1
# added a check to see if a resource pool needs another server.
env.process(Server_Management.check_servers_leaving(env,servers.computer))
np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env, servers))
env.run(until = UNTIL)

got curious and coded it out, I need to go dark for a little while and get some paying work done
"""
Simulation of a dynamic server pool
Server pool starts empty and servers are added as needed, but there is a delay
simulating start up time before the server is available to fulfill a resource request
After a server is started a check is made to see if the server is still needed before
it is added to the resouce pool
Programmer: Matt
Wrote original version
Programmer: Michael R. Gibbs
Added server check for dynamicaly adding servers
servers are returned to resouce pool only if needed (get queue size > 0)
"""
import simpy
import numpy as np
LAM = 8 #arival rate of jobs
MU = 2 #service rate
ALPHA = 12 #set up rate
NUM_SERVERS = 5
MAX_NUM_JOB = 50 #10000000000
UNTIL = 10
server_cnt = 0
job_cnt = 0
start_up_list = []
def generate_interarrival():
return np.random.exponential(1/LAM)
def generate_service():
return np.random.exponential(1/MU)
def switch_on():
return np.random.exponential(1/ALPHA)
def return_server(env,servers):
"""
checks if the server is still needed,
if so add back to the resource pool so waiting request can be filled
else, do not add back to resource pool simulating shutdown
"""
global server_cnt, start_up_list, job_cnt
if len(servers.get_queue) > 0:
# server is still needed
yield servers.put(1)
print('{0:.5f}'.format(env.now), "queuing server --")
if server_cnt > job_cnt:
# have a extra server, try to kill starting up server
# first clean up events that have already happend
i = len(start_up_list)-1
while i >= 0:
e = start_up_list[i]
if e.triggered:
start_up_list.pop(i)
i -=1
# kill last added startup process hoping that is the one with longest time before start up finishes
if len(start_up_list) > 0:
e = start_up_list.pop()
e.interrupt()
print('{0:.5f}'.format(env.now), "killing start up server --------------------------------")
else:
print('{0:.5f}'.format(env.now), "shutting down server --")
server_cnt -= 1
def check_servers(env, servers):
"""
Checks the server pool to see if the pool has any avalable servers
if not then add a server, (there will be a delay before added server becomes available)
after the start up delay, check again to see if the server is still needed
Call this without a yield so it does not block if a server is added
"""
global server_cnt
print('{0:.5f}'.format(env.now), "checking server pool", "requests:",len(servers.get_queue), "idel:", servers.level, "servers:", server_cnt)
if len(servers.get_queue) >= servers.level and server_cnt < NUM_SERVERS:
# will need another server
server_cnt += 1
d = switch_on()
startT = env.now + d
print('{0:.5f}'.format(env.now), "adding a server at " + '{0:.5f}'.format(startT) + " --")
try: # catch interrupts exceptions
# start up
yield env.timeout(d) #switch on time
# check if server is still needed
if len(servers.get_queue) > 0:
# still need it so add
yield servers.put(1)
print('{0:.5f}'.format(env.now), "added a server--")
else:
print('{0:.5f}'.format(env.now), "server not needed, not added--")
server_cnt -=1
except:
server_cnt -= 1
print('{0:.5f}'.format(env.now), "server starting at " + '{0:.5f}'.format(startT) + " has been killed --")
class Generate_Job():
def arriving_job(env, servers):
global num_current_jobs, num_server_on, leaving_time_list
for i in range(MAX_NUM_JOB):
job = Job(name="Job%01d" % (i))
yield env.timeout(generate_interarrival())
print('{0:.5f}'.format(env.now), job.name, "arrives")
env.process(job.handling(env,servers))
class Room: # A room containing servers (resource)
def __init__(self, env):
self.computer = simpy.Container(env, capacity = 10000, init = 0)
class Job(object):
def __init__(self,name):
self.name = name
def handling(self, env, servers):
global start_up_list, job_cnt
# added a check to see if a resource pool needs another server.
job_cnt += 1
start_evt = env.process(check_servers(env,servers.computer))
start_up_list.append(start_evt)
print('{0:.5f}'.format(env.now), self.name, "requesting a server--")
with servers.computer.get(1) as req:
yield req
# if the queue is empty then the req is never filled and the next lines are never called
# need to do this before the rescource requests
#
# yield env.timeout(switch_on()) #switch on time
# yield servers.server.put(1)
print('{0:.5f}'.format(env.now), self.name, "occupies a server--")
yield env.timeout(generate_service()) #service time
print('{0:.5f}'.format(env.now), self.name, "leaves")
# containers do not return a resouce at the end of a "with"
# added a put
#yield servers.computer.put(1)
job_cnt -= 1
yield env.process(return_server(env,servers.computer))
np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env, servers))
env.run(until = UNTIL)

So instead of interrupting the adding of a server, I just added a check after the start up delay to see if the resource is still needed. This simplifies trying to figure which server start up to cancel and avoids having to set up exception handling
I also added return_server process that only adds the server back to the resource pool if the request queue is not empty
The next enhancement would be to allow one or two servers to stay on stand by to help with average response time.
let me know if this works for you
"""
Simulation of a dynamic server pool
Server pool starts empty and servers are added as needed, but there is a delay
simulating start up time before the server is available to fulfill a resource request
After a server is started a check is made to see if the server is still needed before
it is added to the resouce pool
Programmer: Matt
Wrote original version
Programmer: Michael R. Gibbs
Added server check for dynamicaly adding servers
servers are returned to resouce pool only if needed (get queue size > 0)
"""
import simpy
import numpy as np
LAM = 8 #arival rate of jobs
MU = 2 #service rate
ALPHA = 12 #set up rate
NUM_SERVERS = 5
MAX_NUM_JOB = 10000000000
UNTIL = 10
def generate_interarrival():
return np.random.exponential(1/LAM)
def generate_service():
return np.random.exponential(1/MU)
def switch_on():
return np.random.exponential(1/ALPHA)
def return_server(env,servers):
"""
checks if the server is still needed,
if so add back to the resource pool so waiting request can be filled
else, do not add back to resource pool simulating shutdown
"""
if len(servers.get_queue) > 0:
# server is still needed
yield servers.put(1)
print('{0:.5f}'.format(env.now), "queuing server --")
else:
print('{0:.5f}'.format(env.now), "shutting down server --")
def check_servers(env, servers):
"""
Checks the server pool to see if the pool has any avalable servers
if not then add a server, (there will be a delay before added server becomes available)
after the start up delay, check again to see if the server is still needed
Call this without a yield so it does not block if a server is added
"""
print('{0:.5f}'.format(env.now), "checking server pool", "requests:",len(servers.get_queue), "servers:", servers.level)
if len(servers.get_queue) >= servers.level:
# will need another server
d = switch_on()
startT = env.now + d
print('{0:.5f}'.format(env.now), "adding a server at " + '{0:.5f}'.format(startT) + " --")
# start up
yield env.timeout(d) #switch on time
# check if server is still needed
if len(servers.get_queue) > 0:
# still need it so add
yield servers.put(1)
print('{0:.5f}'.format(env.now), "added a server--")
else:
print('{0:.5f}'.format(env.now), "server not needed, not added--")
class Generate_Job():
def arriving_job(env, servers):
global num_current_jobs, num_server_on, leaving_time_list
for i in range(MAX_NUM_JOB):
job = Job(name="Job%01d" % (i))
yield env.timeout(generate_interarrival())
print('{0:.5f}'.format(env.now), job.name, "arrives")
env.process(job.handling(env,servers))
class Room: # A room containing servers (resource)
def __init__(self, env):
self.computer = simpy.Container(env, capacity = 10000, init = 0)
class Job(object):
def __init__(self,name):
self.name = name
def handling(self, env, servers):
# added a check to see if a resource pool needs another server.
env.process(check_servers(env,servers.computer))
print('{0:.5f}'.format(env.now), self.name, "requesting a server--")
with servers.computer.get(1) as req:
yield req
# if the queue is empty then the req is never filled and the next lines are never called
# need to do this before the rescource requests
#
# yield env.timeout(switch_on()) #switch on time
# yield servers.server.put(1)
print('{0:.5f}'.format(env.now), self.name, "occupies a server--")
yield env.timeout(generate_service()) #service time
print('{0:.5f}'.format(env.now), self.name, "leaves")
# containers do not return a resouce at the end of a "with"
# added a put
#yield servers.computer.put(1)
yield env.process(return_server(env,servers.computer))
np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env, servers))
env.run(until = UNTIL)

Related

How to use same proxy until specific response status?

I have currently written a code that has multiple threads (as example I used 50 threads) and for each thread only one proxy is allowed to be in one of these threads (meaning that 1 proxy cannot be in two threads).
import contextlib
import random
import threading
import time
import requests
my_proxies = [
'http://140.99.107.100:2100',
'http://140.99.107.101:2100',
'http://140.99.107.102:2100',
'http://140.99.107.103:2100',
'http://140.99.107.104:2100',
'http://140.99.107.105:2100',
'http://140.99.107.106:2100',
'http://140.99.107.107:2100',
'http://140.99.107.108:2100',
'http://140.99.107.109:2100',
'http://140.99.107.110:2100',
'http://140.99.107.111:2100',
'http://140.99.107.112:2100',
'http://140.99.107.113:2100',
'http://140.99.107.114:2100',
'http://140.99.107.115:2100',
'http://140.99.107.116:2100',
'http://140.99.107.117:2100',
'http://140.99.107.118:2100',
'http://140.99.107.119:2100',
'http://140.99.107.120:2100',
'http://140.99.107.121:2100',
'http://140.99.107.122:2100',
]
# --------------------------------------------------------------------------- #
class AvailableProxiesManager:
_proxy_lock: threading.Lock = threading.Lock()
def __init__(self):
self._proxy_dict = dict.fromkeys(my_proxies, True)
#property
#contextlib.contextmanager
def proxies(self):
"""
Context manager that yields a random proxy from the list of available proxies.
:return: dict[str, str] - A random proxy.
"""
proxy = None
with self._proxy_lock:
while not proxy:
if available := [att for att, value in self._proxy_dict.items() if value]:
proxy = random.choice(available)
self._proxy_dict[proxy] = False
else:
print('Waiting ... no proxies available')
time.sleep(.2)
yield proxy
self._proxy_dict[proxy] = True # Return the proxy to the list of available proxies
# --------------------------------------------------------------------------- #
available_proxies = AvailableProxiesManager()
def main():
while True:
with available_proxies.proxies as proxy:
response = requests.get('https://httpbin.org/ip', proxies={'https': proxy})
if response.status_code == 403:
print('Lets put proxy on cooldown for 10 minutes and try with new one!')
time.sleep(120)
if __name__ == '__main__':
threads = []
for i in range(50):
t = threading.Thread(target=main)
threads.append(t)
t.start()
time.sleep(1)
However my problem is that currently for every while True that is going on, it uses a new random proxy and instead what I am trying to achieve is that I want the same proxy to be used in the same thread until the response status is 403. That means that in the beginning if thread-1 gets the proxy: http://140.99.107.100:2100 then it should be used in thread-1 until it gets 403.
My question is, how can I be able to make the same proxy to be used until it hits response 403?
Expect:
Proxy to be the same until 403
Actual:
New proxy for every GET requests
What if you stop using a context manager,
(remove #contextlib.contextmanager)
and do something like this:
def main():
proxy = next(available_proxies.proxies)
while True:
response = requests.get('https://httpbin.org/ip', proxies={'https': proxy})
if response.status_code == 403:
proxy = next(available_proxies.proxies)
time.sleep(120)
Hope that helps, good luck !

Can I stop waiting for threads to finish if one of them produced results?

Im making a bunch of GET requests to about a few hundred different API endpoints on different servers. In one of these endpoints there is some information that i want to fetch and return.
After any of these requests return something to me, i want to terminate the other threads and exit. Some requests are almost instant, some can take up to 20 seconds to finish.
If i happen to find the info in 2 seconds, i don't want for 20 seconds to elapse before i can resume work.
Currently I'm doing things like this:
threads = list()
for s in silos: #here i create all the requests
t = Thread(target=process_request, args=(my, args, here))
t.name = "{} - {}".format(some, name)
threads.append(t)
Then I do:
print("Threads: {}".format(len(threads))) # 100 - 250 of them
[ t.start() for t in threads ]
[ t.join() for t in threads ]
process_request() simply makes the get request and stores the result inside a dict if the status_code == 200.
I'm using the requests and threading modules.
If you use the multiprocess pool, then you can terminate the pool as soon as the first response arrives:
import multiprocessing as mp
import time
pool = None
def make_get_request(inputs):
print('Making get request with inputs ' + str(inputs))
time.sleep(2)
return 'dummy response for inputs ' + str(inputs)
def log_response(response):
print("Got response = " + response)
pool.terminate()
def main():
global pool
pool = mp.Pool()
for i in range(10):
pool.apply_async(make_get_request, args = (i,), callback = log_response)
pool.close()
pool.join()
if __name__ == '__main__':
main()

How do I sleep for long periods with PyQt threads?

I have a number of certain objects which need to run a specific function at specific ever-changing intervals, again and again, until they decide they are done.
For example, one object may need to wait 30 seconds, run, wait 60 seconds, run, wait 10 seconds, run... You get the point, and this could be going on for 30-120 different objects, running the exact same kind of function.
I was thinking that simply having a function that sleeps for the exact interval would solve my problem, but, correct me if I'm wrong, I remembered that thread pools can only run a certain number of threads at any given time (12 for me). How do I get around this limit?
class Thing(object):
def getCurrentPeriod(self):
return random.randint(5, 30) # Some ever changing period of time
def refresh(self):
doThings() # A long running task that is disk and network intensive
def waitRefresh(self):
period = self.getCurrentPeriod()
time.sleep(period) # Wait that period out
self.refresh()
return self.needRefresh()
# Boolean if it needs to restart - Not sure about how to reschedule,
# or specifically where to connect the worker emit when it finishes
# to make sure this *specific* Thing obj gets it's waitRefresh func called again.
class App(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.threadpool = QThreadPool()
# Add initial objects to pool (other portions of app may add more over time)
for thing in self.acquireThings():
worker = Worker(thing.waitRefresh)
self.threadpool.start(worker)
Doesn't include the WorkerSignals class nor the QRunnable subclass, this example includes what I usually do. The example is tackling the same problem, but in a (most likely) inefficient way.
edit: New example with complete working example of how time.sleep does not pause the thread and allow others to work. I feel that async may be the only implementation, but is there a quick fix so I don't have to alter my entire app?
Here's what it looks like when you try to sleep more than 12 threads.
The ultimate solution came when I decided to actually try the QTimer class. Perhaps there are more optimized solutions, but this one seems to hit all the checkboxes, even if it's worryingly simple.
import random
import time
import traceback
from functools import partial
from PyQt5.QtCore import *
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import *
class WorkerSignals(QObject):
"""
Represents the signals a Worker can emit.
"""
finished = pyqtSignal()
starting = pyqtSignal(int) # ID of thread
result = pyqtSignal(tuple) # Tuple refresh result, result and ID
class Worker(QRunnable):
"""
A worker designed to tell when it's starting, when it's finished and the result.
Designed to work around Thread.refresh().
"""
def __init__(self, fn, thread_id, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.id = thread_id
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#pyqtSlot()
def run(self):
"""
Runs a given method, and emits the result with the Worker's coordinated ID.
"""
try:
self.signals.starting.emit(self.id) # Thread is now finally ready to work.
result = self.fn(*self.args, **self.kwargs) # Refresh Thread!
self.signals.result.emit(result) # Thread is finished, emit result tuple.
except:
traceback.print_exc()
finally:
self.signals.finished.emit() # Done
class Thread(object):
"""
Basic Rules for a Thread Object:
Cannot store the next timestamp on the object (it's a database object, I don't believe it's good practice
to be creating sessions over and over to simply read/write the access time.
ID and Active are allowed as booleans.
"""
i = -1
def __init__(self):
self.id = Thread.nextID()
self.active = True
self.refreshes = 0
def refresh(self) -> tuple:
"""
'Refreshes' a thread. Waits a specific period, then decides whether Thread object should be deactivated or
returned from additional refreshes. Chance of deactivation lowers with each refresh.
:return: The refresh result, a tuple with a boolean and the thread's ID (for identifying it later)
"""
# Represents my SQL Alchemy Model's refresh() function
self.refreshes += 1
time.sleep(random.randint(2, 5))
if random.random() <= max(0.1, 1.0 - ((self.refreshes + 5) / 10)):
self.active = False
return self.active, self.id
#staticmethod
def getRefreshTime() -> float:
"""
Represents the amount of time before a thread should be refreshed.
Should NOT be used to determine whether the thread is still active or not.
:return: The time period that should be waited.
"""
return random.uniform(10, 300)
#staticmethod
def nextID() -> int:
"""
Returns integer thread IDs in sequence to remove possibility of duplicate IDs.
:return: Integer Thread ID
"""
Thread.i += 1
return Thread.i
def __repr__(self):
return f'Thread(id={self.id} active={self.active})'
class MainWindow(QMainWindow):
"""
GUI containing a Label, Button and ListWidget showing all the active sleeping/working threads.
Manages a threadpool, a number of background singleshot timers, etc.
"""
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Widgets Setup
layout = QVBoxLayout()
self.list = QListWidget()
self.l = QLabel("Total Active: 0")
self.button = QPushButton("Refresh List")
self.button.pressed.connect(self.refreshList)
self.button.setDisabled(True)
layout.addWidget(self.l)
layout.addWidget(self.button)
layout.addWidget(self.list)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
# Periodically add threads to the pool.
self.poolTimer = QTimer()
self.poolTimer.setInterval(5_000)
self.poolTimer.timeout.connect(self.addThreads)
# Threading Setup
self.threadpool = QThreadPool()
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
self.active, self.threads = {}, {}
# Add a number of threads to start with.
for _ in range(random.randint(5, 16)):
self.setupThread(Thread())
self.poolTimer.start()
def refreshList(self):
"""
Refreshes the ListWidget in the GUI with all the active/sleeping/working threads.
"""
self.list.clear()
bold = QFont()
bold.setBold(True)
active = 0
for thread in self.threads.values():
item = QListWidgetItem(
f'Thread {thread.id}/{thread.refreshes}')
# Bold a thread if it's working
if self.active[thread.id]:
active += 1
item.setFont(bold)
self.list.addItem(item)
self.l.setText(f'Total Active: {active}/{len(self.threads)}')
def refreshResult(self, result) -> None:
"""
When a thread is finished, the result determines it's next course of action, which is either
to return to the pool again, or delete itself.
:param result: A tuple containing the result (bool) and the connected Thread ID.
"""
self.active[result[1]] = False
if result[0]:
print(f'Restarting Thread {result[1]}')
self.setupThread(self.threads[result[1]]) # Add by ID, which would normally be a database GET
else:
print(f'Thread {result[1]} shutting down.')
del self.active[result[1]]
del self.threads[result[1]]
self.refreshList()
def updateActivity(self, thread_id) -> None:
"""
Connected to the starting signal, helps signal when a thread is actually being refreshed.
:param thread_id: The Thread ID
"""
print(f'Thread {thread_id} is now active/working.')
self.active[thread_id] = True
def refresh(self, thread):
"""
Adds a new worker to the threadpool to be refreshed.
Can't be considered a real start to the thread.refresh function, as the pool has a max of 12 workers at any time.
The 'starting' signal can tell us when a specific thread is actually being refreshed, and is represented
as a Bold element in the list.
:param thread: A thread instance.
"""
print(f'Adding Thread {thread.id} to the pool.')
worker = Worker(thread.refresh, thread_id=thread.id)
worker.signals.result.connect(self.refreshResult)
worker.signals.starting.connect(self.updateActivity)
self.threadpool.start(worker)
# self.active[thread.id] = True
self.refreshList()
def setupThread(self, thread) -> None:
"""
Adds a new timer designated to start a specific thread.
:param thread: A thread instance.
"""
self.active[thread.id] = False
self.threads[thread.id] = thread
t = QTimer()
period = thread.getRefreshTime()
t.singleShot(period * 1000, partial(self.refresh, thread=thread))
print(f'Thread {thread.id} will start in {period} seconds.')
self.refreshList()
def addThreads(self):
"""
Adds a number of threads to the pool. Called automatically every couple seconds.
"""
add = max(0, 30 + random.randint(-5, 5) - len(self.threads))
if add > 0:
print(f'Adding {add} thread{"s" if add > 1 else ""}.')
for _ in range(add):
self.setupThread(Thread())
app = QApplication([])
window = MainWindow()
app.exec_()
When a Thread is requested, a Timer is created and singleShot is fired on an extra function that will add it to the threadpool. This threadpool can handle up to 12 refreshing continious 'refreshing' threads, and signals allow the GUI to update the moment a change is found.
Thousands of 'Thread' objects can be waiting and it seems singleShot is capable of adding them to the pool exactly when they need to be.
Signals help differentiate when a thread is sleeping, working and active (but inactive Thread objects are immediately removed).
The only caveats I can think of with this program is:
1) Can a QThread implementation beat it?
2) What happens to the QTimer once it's singleshot function has executed and fired? Will they be properly GC'd, or can thousands build up in the background consuming resources?

Python Tornado: consuming external Queue from not coroutine

I have the following situation: Using python 3.6 and Tornado 5.1 to receive client requests by web socket. Some of these requests require you to invoke an external processing, which returns a queue and then deposits results periodically in it. These results are transmitted via websocket to the clients.
External processing is NOT a coroutine, so I invoke it using run_in_executor.
My problem:
When the response time of the external processing is very large, the run_in_executor reaches the maximum number of workers (default: number of processors x 5)!
Is it safe to increase the maximum number of workers?
Or is another solution recommended? !!
Below a simplified code.
From already thank you very much!!!!
#########################
## SERVER CODE ##
#########################
from random import randint
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
from random import randint
from tornado import gen
import threading
import asyncio
import queue
import time
class WSHandler(tornado.websocket.WebSocketHandler):
"""entry point for all WS request"""
def open(self):
print('new connection. Request: ' + str(self.request))
async def on_message(self, message):
# Emulates the subscription to an external object
# that returns a queue to listen
producer = Producer()
q = producer.q
while True:
rta = await tornado.ioloop.IOLoop.current().run_in_executor(None, self.loop_on_q, q)
if rta != None:
await self.write_message(str(rta))
else:
break
def on_close(self):
print('connection closed. Request: ' + str(self.request) +
'. close_reason: ' + str(self.close_reason) +
'. close_code: ' + str(self.close_code) +
'. get_status: ' + str(self.get_status()))
def loop_on_q(self, q):
rta = q.get()
return rta
class Producer:
def __init__(self):
self.q = queue.Queue()
t = threading.Thread(target=self.start)
t.daemon = True
t.start()
def start(self):
count = 1
while True:
# time.sleep(randint(1,5))
if count < 100:
self.q.put(count)
else:
self.q.put(None)
break
time.sleep(50)
count += 1
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
asyncio.set_event_loop(asyncio.new_event_loop())
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
print('SRV START')
tornado.ioloop.IOLoop.instance().instance().start()
#########################
## CLIENT CODE ##
#########################
# If you run it more than 20 times in less than 50 seconds ==> Block
# (number of processors x 5), I have 4 cores
from websocket import create_connection
def conect():
url = 'ws://localhost:8888/ws'
ws = create_connection(url)
print('Conecting')
return ws
print('Conecting to srv')
con_ws = conect()
print('Established connection. Sending msg ...')
msj = '{"type":"Socket"}'
con_ws.send(msj)
print('Package sent. Waiting answer...')
while True:
result = con_ws.recv()
print('Answer: ' + str(result))
Is it safe to increase the maximum number of workers Yes, up to a certain fixed amount which can be calculated with load testing.
Or is another solution recommended? If you reach workers limit you can move workers to multiple separated servers (this approach is called horizontal scaling) and pass jobs to them with a message queue. See Celery as a batteries-included-solution or RabbitMQ, Kafka etc. if you prefer to write everything by yourself.

Python Tweepy streaming with multitasking

in Python 2.7 I am successful in using the following code to listen to a direct message stream on an account:
from tweepy import Stream
from tweepy import OAuthHandler
from tweepy import API
from tweepy.streaming import StreamListener
# These values are appropriately filled in the code
consumer_key = '######'
consumer_secret = '######'
access_token = '######'
access_token_secret = '######'
class StdOutListener( StreamListener ):
def __init__( self ):
self.tweetCount = 0
def on_connect( self ):
print("Connection established!!")
def on_disconnect( self, notice ):
print("Connection lost!! : ", notice)
def on_data( self, status ):
print("Entered on_data()")
print(status, flush = True)
return True
# I can add code here to execute when a message is received, such as slicing the message and activating something else
def on_direct_message( self, status ):
print("Entered on_direct_message()")
try:
print(status, flush = True)
return True
except BaseException as e:
print("Failed on_direct_message()", str(e))
def on_error( self, status ):
print(status)
def main():
try:
auth = OAuthHandler(consumer_key, consumer_secret)
auth.secure = True
auth.set_access_token(access_token, access_token_secret)
api = API(auth)
# If the authentication was successful, you should
# see the name of the account print out
print(api.me().name)
stream = Stream(auth, StdOutListener())
stream.userstream()
except BaseException as e:
print("Error in main()", e)
if __name__ == '__main__':
main()
This is great, and I can also execute code when I receive a message, but the jobs I'm adding to a work queue need to be able to stop after a certain amount of time. I'm using a popular start = time.time() and subtracting current time to determine elapsed time, but this streaming code does not loop to check the time. I just waits for a new message, so the clock is never checked so to speak.
My question is this: How can I get streaming to occur and still track time elapsed? Do I need to use multithreading as described in this article? http://www.tutorialspoint.com/python/python_multithreading.htm
I am new to Python and having fun playing around with hardware attached to a Raspberry Pi. I have learned so much from Stackoverflow, thank you all :)
I'm not sure exactly how you want to decide when to stop, but you can pass a timeout argument to the stream to give up after a certain delay.
stream = Stream(auth, StdOutListener(), timeout=30)
That will call your listener's on_timeout() method. If you return true, it will continue streaming. Otherwise, it will stop.
Between the stream's timeout argument and your listener's on_timeout(), you should be able to decide when to stop streaming.
I found I was able to get some multithreading code the way I wanted to. Unlike this tutorial from Tutorialspoint which gives an example of launching multiple instances of the same code with varying timing parameters, I was able to get two different blocks of code to run in their own instances
One block of code constantly adds 10 to a global variable (var).
Another block checks when 5 seconds elapses then prints var's value.
This demonstrates 2 different tasks executing and sharing data using Python multithreading.
See code below
import threading
import time
exitFlag = 0
var = 10
class myThread1 (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
#var counting block begins here
print "addemup starting"
global var
while (var < 100000):
if var > 90000:
var = 0
var = var + 10
class myThread2 (threading.Thread):
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
#time checking block begins here and prints var every 5 secs
print "checkem starting"
global var
start = time.time()
elapsed = time.time() - start
while (elapsed < 10):
elapsed = time.time() - start
if elapsed > 5:
print "var = ", var
start = time.time()
elapsed = time.time() - start
# Create new threads
thread1 = myThread1(1, "Thread-1", 1)
thread2 = myThread2(2, "Thread-2", 2)
# Start new Threads
thread1.start()
thread2.start()
print "Exiting Main Thread"
My next task will be breaking up my twitter streaming in to its own thread, and passing direct messages received as variables to a task queueing program, while hopefully the first thread continues to listen for more direct messages.

Resources