Using Python's asyncio 'await' in ros2 callbacks: 'RuntimeError: await wasn't used with future' - python-asyncio

I'm trying to call an asyncio async function from a ROS2 callback as seen in the code below. The callback always throws an error 'RuntimeError: await wasn't used with future' and I can't figure out why. It doesn't seem to have this error when awaiting a custom async function that doesn't await anything itself (see 'test_async()').
Minimal example
Subscriber - foo
import asyncio, rclpy
from rclpy.node import Node
from std_msgs.msg import Bool
from concurrent.futures import ThreadPoolExecutor
import rclpy.qos as qos
from rclpy.qos import QoSProfile
class Foo(Node):
def __init__(self):
super().__init__('foo')
# Setup test subscriber
qos_profile = QoSProfile(
reliability=qos.ReliabilityPolicy.BEST_EFFORT,
durability=qos.DurabilityPolicy.TRANSIENT_LOCAL,
history=qos.HistoryPolicy.KEEP_LAST,
depth=1
)
self.test_sub = self.create_subscription(
Bool,
'/foo_sub',
self.clbk,
qos_profile)
# Test callback function
async def clbk(self, msg):
self.get_logger().info(f'Received: {msg.data}. About to test async')
# This doesn't cause a problem
await self.test_async()
# This doesn't work
self.get_logger().info('About to sleep')
await asyncio.sleep(3)
# Workaround: This appears to run sleep in a separate thread.
# executor = ThreadPoolExecutor(max_workers=1)
# asyncio.get_event_loop().run_in_executor(executor, asyncio.run, asyncio.sleep(3))
# executor.shutdown(wait=True)
# The workaround doesn't work when getting a returned value
# executor = ThreadPoolExecutor(max_workers=1)
# asyncio.get_event_loop().run_in_executor(executor, asyncio.run, self.sleep_with_return())
# executor.shutdown(wait=True)
self.get_logger().info('Clbk complete')
# Working async function
async def test_async(self):
self.get_logger().info('Test async works!')
# Workaround failure case
async def sleep_with_return(self):
await asyncio.sleep(3)
return True
async def async_main():
rclpy.init()
# Create node and spin
foo = Foo()
rclpy.spin(foo)
def main():
asyncio.run(async_main())
if __name__ == '__main__':
main()
Publisher - bar
import asyncio, rclpy
from rclpy.node import Node
from std_msgs.msg import Bool
import rclpy.qos as qos
from rclpy.qos import QoSProfile
class Bar(Node):
def __init__(self):
super().__init__('bar')
# Setup test publisher
qos_profile = QoSProfile(
reliability=qos.ReliabilityPolicy.BEST_EFFORT,
durability=qos.DurabilityPolicy.TRANSIENT_LOCAL,
history=qos.HistoryPolicy.KEEP_LAST,
depth=1
)
self.test_pub = self.create_publisher(Bool, '/foo_sub', qos_profile)
def send_msg(self):
msg = Bool()
msg.data = True
self.test_pub.publish(msg)
def main():
rclpy.init()
# Create node
bar = Bar()
# Send messages
while True:
input('Press enter when you want to send msg')
bar.send_msg()
if __name__ == '__main__':
main()
Output
After running both nodes and sending a msg from bar (by pressing 'enter'), this is foo's error message:
[INFO] [1674748995.662302006] [foo]: Received: True. About to test async
[INFO] [1674748995.662621572] [foo]: Test async works!
[INFO] [1674748995.662859403] [foo]: About to sleep
Traceback (most recent call last):
File "/home/harvey/px4_ros_com_ros2/install/swarm_load_carry/lib/swarm_load_carry/foo", line 33, in <module>
sys.exit(load_entry_point('swarm-load-carry==0.0.0', 'console_scripts', 'foo')())
File "/home/harvey/px4_ros_com_ros2/install/swarm_load_carry/lib/python3.10/site-packages/swarm_load_carry/foo.py", line 74, in main
asyncio.run(async_main())
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
return future.result()
File "/home/harvey/px4_ros_com_ros2/install/swarm_load_carry/lib/python3.10/site-packages/swarm_load_carry/foo.py", line 71, in async_main
rclpy.spin(foo)
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 222, in spin
executor.spin_once()
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 713, in spin_once
raise handler.exception()
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/task.py", line 239, in __call__
self._handler.send(None)
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 418, in handler
await call_coroutine(entity, arg)
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 343, in _execute_subscription
await await_or_execute(sub.callback, msg)
File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 104, in await_or_execute
return await callback(*args)
File "/home/harvey/px4_ros_com_ros2/install/swarm_load_carry/lib/python3.10/site-packages/swarm_load_carry/foo.py", line 39, in clbk
await asyncio.sleep(3)
File "/usr/lib/python3.10/asyncio/tasks.py", line 605, in sleep
return await future
RuntimeError: await wasn't used with future
[ros2run]: Process exited with failure 1
As you can see in the commented out section in 'clbk', I have found a workaround by running asyncio.run in a separate thread. This is unideal however as it makes the code more complex and I can't retrieve return values (say if asyncio.sleep(3) actually returned something).
As this works, my guess is it might have something to do with asyncio not being threadsafe and ros2 callbacks running in a different thread (I can't seem to find if this is true), or something to do with where the asyncio event loop is running... I've tried may other workarounds based on this assumption however (such as getting the event loop, using call_soon_threadsafe, setting new event loops) and none seem to work.
Running process
Each node 'foo' and 'bar' are run in their own terminals with 'ros2 run pkg module' where pkg (swarm_load_carry) is the name of the ros2 package in a colcon workspace and module is either foo or bar. I am confident that the workspace, package and launchfiles are set up correctly as they work with other test cases.
System details
Ubuntu 22.04.1
Python 3.10
Ros2 Humble

Related

Is there a way to call `curio.spawn` from within `asyncio.run`

There is a great library that I want to use from a larger project that I'm working on that uses "standard asyncio". Some of the functionality of the library calls curio.spawn which results in an error when called from "standard asyncio". Is there a way to get this to work?
Example code that reproduces the error:
import curio
import asyncio
async def curio_method():
await curio.sleep(1)
async def asyncio_method():
task = await curio.spawn(curio_method())
await task
asyncio.run(asyncio_method())
Result:
Traceback (most recent call last):
File "/tmp/curio_test.py", line 12, in <module>
asyncio.run(asyncio_method())
File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
return future.result()
File "/tmp/curio_test.py", line 8, in asyncio_method
task = await curio.spawn(curio_method())
File "/home/garyvdm/dev/image_builder/bpmagent/ve/lib/python3.9/site-packages/curio/task.py", line 613, in spawn
task = await _spawn(coro)
File "/home/garyvdm/dev/image_builder/bpmagent/ve/lib/python3.9/site-packages/curio/traps.py", line 83, in _spawn
return await _kernel_trap('trap_spawn', coro)
File "/home/garyvdm/dev/image_builder/bpmagent/ve/lib/python3.9/site-packages/curio/traps.py", line 32, in _kernel_trap
result = yield request
RuntimeError: Task got bad yield: ('trap_spawn', <coroutine object curio_method at 0x7fc1b62eeec0>)
sys:1: RuntimeWarning: coroutine 'curio_method' was never awaited
Instead of asyncio.run, you can use curio.run.
But, if you need to use asyncio.run, you can put your curio.run inside your asyncio.run logic. Anyway, I think that is not the idea.

How do you get the main "function" of a command in discord.py?

I'm trying to create a command that sends the code of another command in discord.py. For example, if I had a command for rock paper scissors and i ran .code rps, It would send the code for the rps command. Here is my code so far:
import inspect
import discord
from discord.ext.commands import Bot, cooldown, BucketType, CommandOnCooldown
from discord.ext import commands, tasks
#bot.command(hidden=True)
#commands.is_owner()
async def code(ctx, *, command_name):
"""Sends code of a command"""
com = bot.get_command(command_name)
if com is None:
await ctx.reply(f"Bruh that isn't a command", mention_author=False)
elif com:
source = inspect.getsource(com)
try:
await ctx.reply(f"```\n{source}\n```", mention_author=False)
except discord.HTTPException:
await ctx.reply(f"That command's code is too long for me to send lmao", mention_author=False)
print(source)
But there's an error because it can't find the source code of a command type
Ignoring exception in command code:
Traceback (most recent call last):
File "/Users/Bello/PycharmProjects/larry/venv3.8/lib/python3.8/site-packages/discord/ext/commands/core.py", line 85, in wrapped
ret = await coro(*args, **kwargs)
File "/Users/Bello/PycharmProjects/larry/venv3.8/test.py", line 351, in code
source = inspect.getsource(com)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 985, in getsource
lines, lnum = getsourcelines(object)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 967, in getsourcelines
lines, lnum = findsource(object)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 780, in findsource
file = getsourcefile(object)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 696, in getsourcefile
filename = getfile(object)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 676, in getfile
raise TypeError('module, class, method, function, traceback, frame, or '
TypeError: module, class, method, function, traceback, frame, or code object was expected, got Command
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/Bello/PycharmProjects/larry/venv3.8/lib/python3.8/site-packages/discord/ext/commands/bot.py", line 902, in invoke
await ctx.command.invoke(ctx)
File "/Users/Bello/PycharmProjects/larry/venv3.8/lib/python3.8/site-packages/discord/ext/commands/core.py", line 864, in invoke
await injected(*ctx.args, **ctx.kwargs)
File "/Users/Bello/PycharmProjects/larry/venv3.8/lib/python3.8/site-packages/discord/ext/commands/core.py", line 94, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: module, class, method, function, traceback, frame, or code object was expected, got Command
Is there a way to find the source code of a command object or figure out a loophole that lets me do that? Thanks!!
Simply use the callback attribute
func = com.callback
source = inspect.getsource(func)
Reference:
Command.callback

How do i create a Ticket command in discord.py?

Hello so i want to make a discord bot that can create tickets but i am not sure on how do it i am using discord.py and i was wondering if anyone can help? i have tried this.
#bot.command()
async def ticket(ctx):
await create_text_channel(name, *, overwrites=None, reason=None, **options)
But it does not do anything and i get this error.
Traceback (most recent call last):
File "C:\Users\Robin\AppData\Roaming\Python\Python37\site-packages\discord\ext\commands\bot.py", line 903, in invoke
await ctx.command.invoke(ctx)
File "C:\Users\Robin\AppData\Roaming\Python\Python37\site-packages\discord\ext\commands\core.py", line 855, in invoke
await injected(*ctx.args, **ctx.kwargs)
File "C:\Users\Robin\AppData\Roaming\Python\Python37\site-packages\discord\ext\commands\core.py", line 94, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: NameError: name 'create_text_channel' is not defined```
There are three mistakes in the code you gave:
create_text_channel() is a discord.Guild class method so it only works with a Guild instance
The name variable isn't defined so you'd have an error.
If you don't need any overwrites or any reason, you don't need to write overwrites=None and reason=None, same goes for *.
In the end, your code would look like this:
#bot.command()
async def ticket(ctx):
await ctx.guild.create_text_channel('Channel Name')
I guess you looked at the documentation and copy pasted the method's title, which is unnecessary, you could have looked at the examples given, eg. channel = await guild.create_text_channel('cool-channel')
If you want to create a hidden channel, there's also this example:
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
guild.me: discord.PermissionOverwrite(read_messages=True)
}
channel = await guild.create_text_channel('secret', overwrites=overwrites)

BrokenPipeError: [WinError 109] The pipe has been ended during data extraction

I am new to multiprocessing in python.I am extracting some features from a list of 70,000 URLs. I have them from 2 different files. After the feature extraction process I pass the result to a list and then to a CSV file.
The code runs but then stops with the error.I tried to catch the error but it produced another one.
Python version = 3.5
from feature_extractor import Feature_extraction
import pandas as pd
from pandas.core.frame import DataFrame
import sys
from multiprocessing.dummy import Pool as ThreadPool
import threading as thread
from multiprocessing import Process,Manager,Array
import time
class main():
lst = None
def __init__(self):
manager = Manager()
self.lst = manager.list()
self.dostuff()
self.read_lst()
def feature_extraction(self,url):
if self.lst is None:
self.lst = []
features = Feature_extraction(url)
self.lst.append(features.get_features())
print(len(self.lst))
def Pool(self,url):
pool = ThreadPool(8)
results = pool.map(self.feature_extraction, url)
def dostuff(self):
df = pd.read_csv('verified_online.csv',encoding='latin-1')
df['label'] = df['phish_id'] * 0
mal_urls = df['url']
df2 = pd.read_csv('new.csv')
df2['label'] = df['phish_id']/df['phish_id']
ben_urls = df2['urls']
t = Process(target=self.Pool,args=(mal_urls,))
t2 = Process(target=self.Pool,args=(ben_urls,))
t.start()
t2.start()
t.join()
t2.join
def read_lst(self):
nw_df = DataFrame(list(self.lst))
nw_df.columns = ['Redirect count','ssl_classification','url_length','hostname_length','subdomain_count','at_sign_in_url','exe_extension_in_request_url','exe_extension_in_landing_url',
'ip_as_domain_name','no_of_slashes_in requst_url','no_of_slashes_in_landing_url','no_of_dots_in_request_url','no_of_dots_in_landing_url','tld_value','age_of_domain',
'age_of_last_modified','content_length','same_landing_and_request_ip','same_landing_and_request_url']
frames = [df['label'],df2['label']]
new_df = pd.concat(frames)
new_df = new_df.reset_index()
nw_df['label'] = new_df['label']
nw_df.to_csv('dataset.csv', sep=',', encoding='latin-1')
if __name__ == '__main__':
start_time = time.clock()
try:
main()
except BrokenPipeError:
print("broken pipe....")
pass
print (time.clock() - start_time, "seconds")
Error Traceback
Process Process-3:
Traceback (most recent call last):
File "F:\Continuum\Anaconda3\lib\multiprocessing\connection.py", line 312, in _recv_bytes
nread, err = ov.GetOverlappedResult(True)
BrokenPipeError: [WinError 109] The pipe has been ended
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "F:\Continuum\Anaconda3\lib\multiprocessing\process.py", line 249, in _bootstrap
self.run()
File "F:\Continuum\Anaconda3\lib\multiprocessing\process.py", line 93, in run
self._target(*self._args, **self._kwargs)
File "H:\Projects\newoproject\src\main.py", line 33, in Pool
results = pool.map(self.feature_extraction, url)
File "F:\Continuum\Anaconda3\lib\multiprocessing\pool.py", line 260, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
File "F:\Continuum\Anaconda3\lib\multiprocessing\pool.py", line 608, in get
raise self._value
File "F:\Continuum\Anaconda3\lib\multiprocessing\pool.py", line 119, in worker
result = (True, func(*args, **kwds))
File "F:\Continuum\Anaconda3\lib\multiprocessing\pool.py", line 44, in mapstar
return list(map(*args))
File "H:\Projects\newoproject\src\main.py", line 26, in feature_extraction
self.lst.append(features.get_features())
File "<string>", line 2, in append
File "F:\Continuum\Anaconda3\lib\multiprocessing\managers.py", line 717, in _callmethod
kind, result = conn.recv()
File "F:\Continuum\Anaconda3\lib\multiprocessing\connection.py", line 250, in recv
buf = self._recv_bytes()
File "F:\Continuum\Anaconda3\lib\multiprocessing\connection.py", line 321, in _recv_bytes
raise EOFError
EOFError
My response is late and does not address the posted problem directly; but hopefully will provide a clue to others who encounter similar errors.
Errors that I encountered:
BrokenPipeError
WinError 109 The pipe has been ended &
WinError 232 The pipe is being closed
Observed with Python 36 on Windows 7, when:
(1) the same async function was submitted multiple times, each time with a different instance of a multiprocessing data store, a Queue in my case (multiprocessing.Manager().Queue())
AND
(2) the references to the Queues were saved in short-life local variables in the enveloping function.
The errors were occurring despite the fact that the Queues, shared with the successfully spawned and executing async-functions, had items and would still be in active use (put() & get()) at the time of exception.
The error consistently occurred when the same async_func was called the 2nd time with a 2nd instance of the Queue. Immediately after apply_async() of the function, the connection to the 1st Queue supplied to the async_func the 1st time, would get broken.
The issue got resolved when the references to the Queues were saved in non-overlapping (like a Queue-list) & longer-life variables (like variables returned to functions higher in the call-stack) in the enveloping function.

Replace pickle with dill and pymongo call

I finally understood example how to replace pickle with dill from the following discussion: pickle-dill.
For example, the following code worked for me
import os
import dill
import multiprocessing
def run_dill_encoded(what):
fun, args = dill.loads(what)
return fun(*args)
def apply_async(pool, fun, args):
return pool.apply_async(run_dill_encoded, (dill.dumps((fun, args)),))
if __name__ == '__main__':
pool = multiprocessing.Pool(5)
results = [apply_async(pool, lambda x: x*x, args=(x,)) for x in range(1,7)]
output = [p.get() for p in results]
print(output)
I tried to apply the same philosophy to pymongo. The following code
import os
import dill
import multiprocessing
import pymongo
def run_dill_encoded(what):
fun, args = dill.loads(what)
return fun(*args)
def apply_async(pool, fun, args):
return pool.apply_async(run_dill_encoded, (dill.dumps((fun, args)),))
def write_to_db(value_to_insert):
client = pymongo.MongoClient('localhost', 27017)
db = client['somedb']
collection = db['somecollection']
result = collection.insert_one({"filed1": value_to_insert})
client.close()
if __name__ == '__main__':
pool = multiprocessing.Pool(5)
results = [apply_async(pool, write_to_db, args=(x,)) for x in ['one', 'two', 'three']]
output = [p.get() for p in results]
print(output)
produces error:
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
File "C:\Python34\lib\multiprocessing\pool.py", line 119, in worker
result = (True, func(*args, **kwds))
File "C:\...\temp2.py", line 10, in run_dill_encoded
return fun(*args)
File "C:\...\temp2.py", line 21, in write_to_db
client = pymongo.MongoClient('localhost', 27017)
NameError: name 'pymongo' is not defined
"""
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:/.../temp2.py", line 32, in <module>
output = [p.get() for p in results]
File "C:/.../temp2.py", line 32, in <listcomp>
output = [p.get() for p in results]
File "C:\Python34\lib\multiprocessing\pool.py", line 599, in get
raise self._value
NameError: name 'pymongo' is not defined
Process finished with exit code 1
What is wrong?
As I mentioned in the comments, you need to put an import pymongo inside the function write_to_db. This is because when the function is serialized, it does not take along any of the global references with it when it is shipped to the other process space.

Resources