Variable passed to multiple threads of the same function - python

I am currently working on a system that sends webhooks to multiple Discord using dhooks.
This is the function used to call the function which sends the webhooks. The length of webhooks is 2 and it's a list of lists.
for hook in webhooks:
thread.start_new_thread(send_hook,(hook, embed, tag))
This is the send_hook function:
def send_hook(hook, embed, tag):
embed.set_footer(text="{} x Will".format(hook[0])) <-- This is the part where the error happens
webhook = Webhook(hook[1])
webhook.send(embed=embed)
The error I am getting is, when I set the footer in line 2 of send_hook, the variable inserted into the embed variable is sometimes sent to the wrong webhooks - almost as if its being over ridden.
As an example:
This is my list webhooks: [["Webhook 1 text", "discordwebhook1"], ["Webhook 2 text", "discordwebhook1"]].
What will happen is that in the channel of discordwebhook1 the footer will say "Webhook 1 text", but in the channel of discordwebhoo2 the footer will say "Webhook 1 text" as-well.
I have tried creating a new variable of the embed in the send_hook function - however this also didn't work (code below).
def send_hook(hook, embed, tag):
new_embed = embed
new_embed.set_footer(text="{} x Will".format(hook[0])) <-- This is the part where the error happens
webhook = Webhook(hook[1])
webhook.send(embed=new_embed)
I appreciate all help!
Thanks

How do you get 'embed'? Notice that 'embed' and 'tag' are always passed to every newly created thread, you need to do a deepcopy of each if necessary
from copy import deepcopy
for hook in webhooks:
thread.start_new_thread(send_hook,(hook, deepcopy(embed), tag))

You are experiencing a race condition. Two threads have access to the same variable, and they are both modifying the variable. The outcome of your program depends on which one reaches the code that changes the variable first.
There are two possible solutions, depending on how you want the problem to be resolved:
If you don't need all threads to share the same value, or if the object is small and cheap to copy, make a copy of the variable you are passing before you pass it, by passing deepcopy(embed) instead of embed - see the solution posted by #user1438644 for code.
If you want all threads to share the same value, or if it is expensive to make copies of the object, you will need to create a lock with my_lock = threading.Lock(), and when you get to the race condition part of your program (i.e., the part that modifies the shared variable), surround it with a context manager with my_lock:, which acquires the lock at the start and returns the lock when finished. For example:
import threading
my_lock = threading.Lock()
# your code here
for hook in webhooks:
threading.Thread(send_hook, args=(hook, embed, tag))
# more code here
def send_hook(hook, embed, tag):
# Ensure embed is only changed by one thread at a time
with my_lock:
print("Lock acquired")
embed.set_footer(text="hello world")
webhook = Webhook(hook[1])
webhook.send(embed=embed)

Related

trio.Event(): Which is “better”: setting and initializing a new Event or checking if someone is waiting for it beforehand?

import trio
work_available = trio.Event()
async def get_work():
while True:
work = check_for_work()
if not work:
await work_available.wait()
else:
return work
def add_work_to_pile(...):
...
if work_available.statistics().tasks_waiting:
global work_available
work_available.set()
work_available = trio.Event()
In this Python-like code example I get work in bursts via add_work_to_pile(). The workers which get work via get_work() are slow. So most of the time add_work_to_pile() is called there will be no one waiting on work_available.
Which is better/cleaner/simpler/more pythonic/more trionic/more intended by the trio developers?
checking if someone is looking for the Event() via statistics().tasks_waiting, like in the example code, ...or...
unconditionally set() setting the Event() and creating a new one each time? (Most of them in vain.)
Furthermore... the API does not really seem to expect regular code to check if someone is waiting via this statistics() call...
I don’t mind spending a couple more lines to make things clearer. But that goes both ways: a couple CPU cycles more are fine for simpler code...
Creating a new Event is roughly the same cost as creating the _EventStatistics object within the statistics method. You'll need to profile your own code to pick out any small difference in performance. However, although it is safe and performant, the intent of statistics across trio's classes is for debug rather than core logic. Using/discarding many Event instances would be relatively more along the intent of the devs.
A more trionic pattern would be to load each work item into a buffered memory channel in place of your add_work_to_pile() method and then iterate on that in the task that awaits get_work. I feel the amount of code is comparable to your example:
import trio
send_chan, recv_chan = trio.open_memory_channel(float('inf'))
async def task_that_uses_work_items():
# # compare
# while True:
# work = await get_work()
# handle_work(work)
async for work in recv_chan:
handle_work(work)
def add_work_to_pile():
...
for work in new_work_set:
send_chan.send_nowait(work)
# maybe your work is coming in from a thread?
def add_work_from_thread():
...
for work in new_work_set:
trio_token.run_sync_soon(send_chan.send_nowait, work)
Furthermore, it's performant because the work items are efficiently rotated through a deque internally. This code would checkpoint for every work item so you may have to jump through some hoops if you want to avoid that.
I think you might want a trio.ParkingLot. It gives more control over parking (i.e. which is like Event.wait()) and unparking (which is like Event.set() except that it doesn't stop future parkers from waiting). But it doesn't have any notion of being set at all so you would need to store that information separately. If you work is naturally Truety when set (e.g. a non-empty list) then that might be easy anyway. Example:
available_work = []
available_work_pl = trio.ParkingLot()
async def get_work():
while not available_work:
await available_work_pl.park()
result = list(available_work)
available_work.clear()
return result
def add_work_to_pile():
available_work.append(foo)
available_work_pl.unpark()
Edit: Replaced "if" with "while" in get_work(). I think if has a race condition: if there are two parked tasks and then add_work_to_pile() gets called twice, then one get_work() would get both work items but the other would still be unparked and return an empty list. Using while instead will make it loop back around until more data is added.
IMHO you don't want an event in the first place. The combination of an array and something that tells the reader there's work in the array is already available as memory channels. They have the additional advantage that you can tell them how much work to accept before the sender stalls.
send_channel, recv_channel = trio.open_memory_channel(10)
get_work = recv_channel.receive
add_work_to_pile = send_channel.send
# both are async functions or use the _nowait() versions

Is there a Function that runs before the bot gets closed

Is there a Function that runs before the bot gets closed
Ex.
#bot.event()
async def on_close(ctx):
export_files()
I'm making a bot that reads all the new messages and adds them to the author's list of words and when the command .get_word_count gets called all of the words that the author has sent will be shown
im: 98
under: 1
the: 1
water: 1
please: 1
help: 1
me: 1
test: 124136624745687697698608
the reason I'm storing the data is that it's more efficient to store and start a new read rather than going through all of the channels and get the word counts
The on_disconnect event. However, do note that it might trigger when no connection was already established, if establishing one fails.
Hoewever I haven't really understood what your use case is and what you need on_disconnect for. There might be a better way.
The best way I have found yet is to subclass commands.Bot and override the close method
class MyBot(commands.Bot):
async def close(self):
pass # you can also use attributes (even custom ones) using self.attr
bot = MyBot(command_prefix="!")
This would be well suited if you need a coroutine

How do I use global variables in separate modules for python?

I have this very nasty python script which is, to say the least not very well managed and I'd like to improve it. The way I thought about doing that is breaking up the code into what they do and then importing them into the main function that runs everything. But some of my defs have a global (like the key word based global) in them, how do I scope those out in separate files exactly?
for example main,py wi have:
import function
message = {}
while (true):
function.function(message)
print(message)
and function,py has :
def function(some variables) :
global message
if (somevariable = something):
message = "xyz"
message in the second file is giving me an error
EDIT: So I see that I need to give a bit more context, there are infact 3 processes (functions) that are running and the global message is what I'm using to pass information between all these threads. So Ideally Id like to separate all the processes as different files and then keep adding them as a new thread. How do I go about this?

In telebot #bot.message_handler() running even if it is kept inside a if statement

I am relatively new to coding and to python. I am trying to make a telegram bot using telebot.
I have a flow in which I have to go. eg: after /start, I should get a text. So I created a running_work_list in which my workflow is arranged in order and after completion, I delete the index 0 elements. 'start' is the first element in the array.
So I tried to implement something like this so that I can comfortably code each step rather than looking at input and deciding.
But despite putting inside an if statement, '#bot.message_handler()' is running even if the condition fails.
if running_work_list[0]=='start':
print('inside if')
#bot.message_handler(commands=['start','new test'])
def start(message):
print('user sent start')
bot.send_message(message.chat.id,welcome_message)
running_work_list.pop(0)
print(f'work flow deciding list {running_work_list}')
#bot.message_handler(content_types=['text','photo','poll'])
def bot_intro(message):
print('here')
print(f'user sent {message.text}')
bot.send_message(message.chat.id,BOT_INTRO_MSG)
Below are the outputs I got. The user had sent /start first and then some other random text.
inside if
user sent start
work flow deciding list ['test_name', 'test_description', 'test_time', 'test_image', 'test_poll']
here
user sent fdf
Is it like the '#bot.message_handler()' will run even if we put it inside an if statement?
Executing #bot.message_handler decorator marks the function to be called when the message matches the filters that you've passed as arguments (content_types or commands). They are called by the telebot library directly and do not execute the if statement after they have been registered.
The proper way to do this is to invert the logic: define the message handler functions at the top level of the program and decide what you should do in the if within the function.

Python 3 (Bot) script stops working

I'm trying to connect to a TeamSpeak server using the QueryServer to make a bot. I've taken advice from this thread, however I still need help.
This is The TeamSpeak API that I'm using.
Before the edits, this was the summary of what actually happened in my script (1 connection):
It connects.
It checks for channel ID (and it's own client ID)
It joins the channel and starts reading everything
If someone says an specific command, it executes the command and then it disconnects.
How can I make it so it doesn't disconnect? How can I make the script stay in a "waiting" state so it can keep reading after the command is executed?
I am using Python 3.4.1.
I tried learning Threading but either I'm dumb or it doesn't work the way I thought it would. There's another "bug", once waiting for events, if I don't trigger anything with a command, it disconnects after 60 seconds.
#Librerias
import ts3
import threading
import datetime
from random import choice, sample
# Data needed #
USER = "thisisafakename"
PASS = "something"
HOST = "111.111.111.111"
PORT = 10011
SID = 1
class BotPrincipal:
def __init__(self, manejador=False):
self.ts3conn = ts3.query.TS3Connection(HOST, PORT)
self.ts3conn.login(client_login_name=USER, client_login_password=PASS)
self.ts3conn.use(sid=SID)
channelToJoin = Bot.GettingChannelID("TestingBot")
try: #Login with a client that is ok
self.ts3conn.clientupdate(client_nickname="The Reader Bot")
self.MyData = self.GettingMyData()
self.MoveUserToChannel(ChannelToJoin, Bot.MyData["client_id"])
self.suscribirEvento("textchannel", ChannelToJoin)
self.ts3conn.on_event = self.manejadorDeEventos
self.ts3conn.recv_in_thread()
except ts3.query.TS3QueryError: #Name already exists, 2nd client connect with this info
self.ts3conn.clientupdate(client_nickname="The Writer Bot")
self.MyData = self.GettingMyData()
self.MoveUserToChannel(ChannelToJoin, Bot.MyData["client_id"])
def __del__(self):
self.ts3conn.close()
def GettingMyData(self):
respuesta = self.ts3conn.whoami()
return respuesta.parsed[0]
def GettingChannelID(self, nombre):
respuesta = self.ts3conn.channelfind(pattern=ts3.escape.TS3Escape.unescape(nombre))
return respuesta.parsed[0]["cid"]
def MoveUserToChannel(self, idCanal, idUsuario, passCanal=None):
self.ts3conn.clientmove(cid=idCanal, clid=idUsuario, cpw=passCanal)
def suscribirEvento(self, tipoEvento, idCanal):
self.ts3conn.servernotifyregister(event=tipoEvento, id_=idCanal)
def SendTextToChannel(self, idCanal, mensajito="Error"):
self.ts3conn.sendtextmessage(targetmode=2, target=idCanal, msg=mensajito) #This works
print("test") #PROBLEM HERE This doesn't work. Why? the line above did work
def manejadorDeEventos(sender, event):
message = event.parsed[0]['msg']
if "test" in message: #This works
Bot.SendTextToChannel(ChannelToJoin, "This is a test") #This works
if __name__ == "__main__":
Bot = BotPrincipal()
threadprincipal = threading.Thread(target=Bot.__init__)
threadprincipal.start()
Prior to using 2 bots, I tested to launch the SendTextToChannel when it connects and it works perfectly, allowing me to do anything that I want after it sends the text to the channel. The bug that made entire python code stop only happens if it's triggered by the manejadorDeEventos
Edit 1 - Experimenting with threading.
I messed it up big time with threading, getting to the result where 2 clients connect at same time. Somehow i think 1 of them is reading the events and the other one is answering. The script doesn't close itself anymore and that's a win, but having a clone connection doesn't looks good.
Edit 2 - Updated code and actual state of the problem.
I managed to make the double connection works more or less "fine", but it disconnects if nothing happens in the room for 60 seconds. Tried using Threading.timer but I'm unable to make it works. The entire question code has been updated for it.
I would like an answer that helps me to do both reading from the channel and answering to it without the need of connect a second bot for it (like it's actually doing...) And I would give extra points if the answer also helps me to understand an easy way to make a query to the server each 50 seconds so it doesn't disconnects.
From looking at the source, recv_in_thread doesn't create a thread that loops around receiving messages until quit time, it creates a thread that receives a single message and then exits:
def recv_in_thread(self):
"""
Calls :meth:`recv` in a thread. This is useful,
if you used ``servernotifyregister`` and you expect to receive events.
"""
thread = threading.Thread(target=self.recv, args=(True,))
thread.start()
return None
That implies that you have to repeatedly call recv_in_thread, not just call it once.
I'm not sure exactly where to do so from reading the docs, but presumably it's at the end of whatever callback gets triggered by a received event; I think that's your manejadorDeEventos method? (Or maybe it's something related to the servernotifyregister method? I'm not sure what servernotifyregister is for and what on_event is for…)
That manejadorDeEventos brings up two side points:
You've declared manejadorDeEventos wrong. Every method has to take self as its first parameter. When you pass a bound method, like self.manejadorDeEventos, that bound self object is going to be passed as the first argument, before any arguments that the caller passes. (There are exceptions to this for classmethods and staticmethods, but those don't apply here.) Also, within that method, you should almost certainly be accessing self, not a global variable Bot that happens to be the same object as self.
If manejadorDeEventos is actually the callback for recv_in_thread, you've got a race condition here: if the first message comes in before your main threads finishes the on_event assignment, the recv_on_thread won't be able to call your event handler. (This is exactly the kind of bug that often shows up one time in a million, making it a huge pain to debug when you discover it months after deploying or publishing your code.) So, reverse those two lines.
One last thing: a brief glimpse at this library's code is a bit worrisome. It doesn't look like it's written by someone who really knows what they're doing. The method I copied above only has 3 lines of code, but it includes a useless return None and a leaked Thread that can never be joined, not to mention that the whole design of making you call this method (and spawn a new thread) after each event received is weird, and even more so given that it's not really explained. If this is the standard client library for a service you have to use, then you really don't have much choice in the matter, but if it's not, I'd consider looking for a different library.

Categories

Resources