ballyregan icon indicating copy to clipboard operation
ballyregan copied to clipboard

Unable to work inside async functions

Open Happ1ness-dev opened this issue 2 years ago • 2 comments

Describe the bug Can't run properly in async functions.

To Reproduce Steps to reproduce the behavior:

  1. Create an async function which would try to fetch proxies via ballyregan.
  2. Call the async function.
  3. See error.

Expected behavior Normal operation, as with synchronous calling.

Screenshots Screenshot_2023-09-28-19-28-11-185_com termux

Desktop (please complete the following information):

  • OS: N/A
  • Browser N/A
  • Version N/A

Smartphone (please complete the following information):

  • Device: Poco F3
  • OS: Android 12
  • Browser N/A
  • Version N/A

Additional context The library seems to work fine with simple synchronous calls, but it's always spitting out an error whenever I'm trying to use it in async functions.

Here's (one of the) the errors I get:

Traceback (most recent call last):                                                                File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/aiohttp/web_protocol.py", line 433, in _handle_request                                                                         resp = await request_handler(request)                                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                         File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/aiohttp/web_app.py", line 504, in _handle                                                                                      resp = await handler(request)                                                                          ^^^^^^^^^^^^^^^^^^^^^^                                                                 File "/storage/emulated/0/tmp/apihost-tts-proxy.py", line 37, in handle_request                   fetch_proxies()                                                                               File "/storage/emulated/0/tmp/apihost-tts-proxy.py", line 18, in fetch_proxies                    raw_proxies = fetcher.get(                                                                                    ^^^^^^^^^^^^                                                                    File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/ballyregan/fetcher.py", line 134, in get                                                                                       proxies = self._gather(                                                                                   ^^^^^^^^^^^^^                                                                       File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/ballyregan/fetcher.py", line 95, in _gather                                                                                    valid_proxies = self._proxy_validator.filter_valid_proxies(                                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                   File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/ballyregan/validator.py", line 129, in filter_valid_proxies                                                                    return self.loop.run_until_complete(                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                          File "/data/data/com.termux/files/usr/lib/python3.11/asyncio/base_events.py", line 629, in run_until_complete                                                                                     self._check_running()                                                                         File "/data/data/com.termux/files/usr/lib/python3.11/asyncio/base_events.py", line 590, in _check_running                                                                                         raise RuntimeError(                                                                         RuntimeError: Cannot run the event loop while another loop is running                           /data/data/com.termux/files/usr/lib/python3.11/asyncio/base_events.py:1907: RuntimeWarning: coroutine 'ProxyValidator._async_filter_valid_proxies' was never awaited                              handle = self._ready.popleft()                                                                RuntimeWarning: Enable tracemalloc to get the object allocation traceback

I even tried using it as normal synchronous, blocking function, but it still throws an error.

Here's ~~my~~ gpt-4's code (lol) I was trying to get working with it:

import random
from aiohttp import web, ClientSession
from ballyregan import ProxyFetcher
from ballyregan.models import Protocols

# A list to hold our proxies and number of uses
proxies = []

MAX_USES = 10  # Maximum number of uses per proxy
REFETCH_NO = 20  # Number of proxies you want to fetch initially and when the list is exhausted

# Prepare the proxy fetcher
fetcher = ProxyFetcher(debug=True)

# Fetch and format proxies
def fetch_proxies():
    proxies.clear()
    raw_proxies = fetcher.get(
        limit=REFETCH_NO,
        protocols=[Protocols.HTTPS, Protocols.SOCKS4, Protocols.SOCKS5]
    )
    for p in raw_proxies:
        proxies.append({
            "proxy": f'{p.ip}:{p.port}',
            "type": {'https': 'HTTPS', 'socks4': 'SOCKS4', 'socks5': 'SOCKS5'}.get(p.protocol.lower()),
            "count": 0,
        })
    print(f'Fetched {len(proxies)} proxies. Proxy list: {proxies}')


async def handle_request(request):
    # Prepare headers for forward request
    headers = {k: v for k, v in request.headers.items()}
    url = 'https://hidden.due/to/privacy/reasons'
    while True:
        if not proxies:  # If no proxies left
            fetch_proxies()

        cur_proxy = random.choice(proxies)
        count = cur_proxy["count"]

        if count >= MAX_USES: # If proxy use count exceed the maximum use limit
            proxies.remove(cur_proxy) # If so, remove the proxy from the list
        else:
            try:
                cur_proxy["count"] += 1
                async with ClientSession() as session:
                    # Correctly format the proxy URL depending on the type
                    proxy_url = {'HTTP': 'http://', 'HTTPS': 'https://', 'SOCKS4': 'socks4://', 'SOCKS5': 'socks5://'}.get(cur_proxy['type'], 'http://') + cur_proxy["proxy"]
                    print(f'Proxy url is {proxy_url}')
                    async with session.request(
                        request.method,
                        url,
                        headers=headers,
                        allow_redirects=False,
                        data=await request.read(),
                        proxy=proxy_url
                    ) as resp:
                        resp_headers = {k: v for k, v in resp.headers.items()}
                        body = await resp.text()
                        break
            except:
                proxies.remove(cur_proxy)  # If the proxy is not working, remove it from the list

    return web.Response(
        headers=resp_headers,
        status=resp.status,
        body=body
    )

app = web.Application()
app.router.add_route('*', '/{tail:.*}', handle_request)
fetch_proxies()  # Fetch proxies initially

try:
    web.run_app(app, port=1337)
except KeyboardInterrupt:
    print("KeyboardInterrupt received, exiting...")

As expected, it works for me at the start, but throws an error every time the script tries to fetch new proxies afterwards.

Happ1ness-dev avatar Sep 28 '23 16:09 Happ1ness-dev

Hi, thank you for opening an issue! Will investigate it and let you know.

idandaniel avatar Sep 28 '23 18:09 idandaniel

tl;dr Create the ProxyFetcher instance inside an async scope for now, I'll fix it in the next few days.

Explanation: The issue is caused by the fact that 'ProxyValidator' initiates it's event loop once in the class init. That means that when you create the 'ProxyFetcher' instance outside an async scope, the 'ProxtValidator' does not recognize any existing event loop and creates one. Then when you run in inside an async scope there will be 2 event loops running, which is not allowed.

idandaniel avatar Oct 04 '23 08:10 idandaniel