UpstreamData / pyasic

A simplified and standardized interface for Bitcoin ASICs.
https://docs.pyasic.org
Apache License 2.0
100 stars 54 forks source link

Read timeout expired #234

Open Combinator78 opened 1 week ago

Combinator78 commented 1 week ago

Sometimes, if a device freezes, the program constantly tries to reach it, and then does not continue working. Is it possible to make it so that if the device does not respond once by timeout, the program continues scanning the next IPs?

stderr: C:agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:50:08 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:51:48 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:53:28 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:55:08 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:56:48 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\agent_new\local_libs\pyasic\rpc\base.py:228 in _send_bytes [WARNING]11/14/24 05:58:28 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - Read timeout expired.

stderr: C:\vekus\agent_new\local_libs\pyasic\rpc\base.py:248 in _read_bytes [WARNING]11/14/24 05:58:50 - AntminerRPCAPI: 172.16.14.241 - ([Hidden] Send Bytes) - API Command Error [WinError 121]

stderr: Traceback (most recent call last): File "C:\agent_new\local_libs\pyasic\miners\base.py", line 496, in _get_data

stderr: miner_data[data_name] = await function(**args_to_send) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\backends\bmminer.py", line 200, in _get_fans rpc_stats = await self.rpc.stats() ^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\rpc\antminer.py", line 8, in stats

stderr: return await super().stats() ^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\rpc\cgminer.py", line 369, in stats return await self.send_command("stats") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\rpc\base.py", line 79, in send_command data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))

stderr: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\rpc\base.py", line 234, in _send_bytes await writer.wait_closed() File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\streams.py", line 350, in wait_closed

stderr: await self._protocol._get_close_waiter(self) File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py", line 286, in _loop_reading

stderr: length = fut.result() ^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\windows_events.py", line 846, in _poll

stderr: value = callback(transferred, key, ov) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stderr: File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\windows_events.py", line 494, in finish_recv return ov.getresult() ^^^^^^^^^^^^^^ OSError: [WinError 121]

The above exception was the direct cause of the following exception:

Traceback (most recent call last): File "C:\agent_new\scan.py", line 255, in

stderr: asyncio.run(gather_miner_data(ip_ranges)) File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 118, in run

stderr: return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "C:\agent_new\scan.py", line 156, in gather_miner_data

stderr: all_results = await asyncio.gather(*[miner.get_data() for miner in miners]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\base.py", line 535, in get_data

stderr: gathered_data = await self._get_data( ^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\base.py", line 498, in _get_data raise APIError( pyasic.errors.APIError: Failed to call fans on L7 (Stock): 172.16.14.241 while getting data.me(),

b-rowan commented 1 week ago

I sort of see what you mean, but not sure I understand what the solution you want here is? Are you gathering all the tasks together and this one is slowing it down?

Combinator78 commented 1 week ago

I collect data from a subnet, but when the program can't reach the IP, it ends with an error (updated above)

b-rowan commented 1 week ago

I collect data from a subnet, but when the program can't reach the IP, it ends with an error (updated above)

I think you can use return_exceptions=True in the gather to handle for that, then you just can log it if any item in the list returned is a subclass of Exception?

Combinator78 commented 1 week ago

do you need to add anything to this piece of code?

async def gather_miner_data(ip_ranges_str):
    all_subnets = ip_range_to_cidr(ip_ranges_str)
    global ip_num
    all_json_results = []

    for subnet in all_subnets:
        network = MinerNetwork.from_subnet(subnet)
        miners = await network.scan()

        all_results = await asyncio.gather(*[miner.get_data() for miner in miners])

        json_results = [format_miner_data_to_json(miner_data) for miner_data in all_results]
        all_json_results.extend(json_results)
        # Format and print the results
        json_results = [format_miner_data_to_json(miner_data) for miner_data in all_results]

        if json_results:
            print(json.dumps(json_results, indent=4))

        for result in json_results:
            if result.get('ip'):
                ip_num += 1
b-rowan commented 1 week ago

Try this:

async def gather_miner_data(ip_ranges_str):
    all_subnets = ip_range_to_cidr(ip_ranges_str)
    global ip_num
    all_json_results = []

    for subnet in all_subnets:
        network = MinerNetwork.from_subnet(subnet)
        miners = await network.scan()

        all_results = await asyncio.gather(
            *[miner.get_data() for miner in miners], return_exceptions=True
        )

        safe_results = []
        for item in all_results:
            if isinstance(item, Exception):
                print(f"Error: {item}")
            else:
                safe_results.append(item)

        json_results = [
            format_miner_data_to_json(miner_data) for miner_data in safe_results
        ]
        all_json_results.extend(json_results)

        if json_results:
            print(json.dumps(json_results, indent=4))

        for result in json_results:
            if result.get("ip"):
                ip_num += 1
Combinator78 commented 1 week ago

Thank you! This helped to catch all the problematic devices

Combinator78 commented 1 week ago

File "C:\agent_new\local_libs\pyasic\miners\base.py", line 498, in _get_data raise APIError( pyasic.errors.APIError: Failed to call pools on Avalon 1246 (Stock): 172.16.43.27 while getting data.

please tell me how to handle this error? it occurs when incorrect pools are specified, but I still need to write this data to all_json_results.

b-rowan commented 1 week ago

File "C:\agent_new\local_libs\pyasic\miners\base.py", line 498, in _get_data raise APIError( pyasic.errors.APIError: Failed to call pools on Avalon 1246 (Stock): 172.16.43.27 while getting data.

please tell me how to handle this error? it occurs when incorrect pools are specified, but I still need to write this data to all_json_results.

This is an issue with only the pools, but the rest of the data is valid? So the idea is you want the rest of the data to write, but to not write the invalid pools?

Combinator78 commented 1 week ago

I want the data to be written even if the pool is incorrect. We have a large hosting platform, and when a client has not paid for the hosting, the technicians deliberately make changes to the pool, for example stratum+tcp://eu.emcd.io:333333333333 And this causes an error

The device in the first worker is transferred to our platform account, and the second and third pools are spoiled so that the device does not move to them. When the client pays, we return the settings back

Now I just make changes to the code in the file base.py from function = getattr(self, getattr(self.data_locations, data_name).cmd) miner_data[data_name] = await function(**args_to_send) except Exception as e: raise APIError( f"Failed to call {data_name} on {self} while getting data." ) from e return miner_data

     to 

            function = getattr(self, getattr(self.data_locations, data_name).cmd)
            miner_data[data_name] = await function(**args_to_send)
        except Exception as e:
            pass
    return miner_data

    But I understand that this is not right.
b-rowan commented 1 week ago

Ah. Yeah something isn't being handled properly downstream then, likely in the pools function. Could you change that pass to raise e and let me know what that raises?

Combinator78 commented 1 week ago

Traceback (most recent call last): File "C:\agent_new\test.py", line 18, in asyncio.run(gather_miner_data()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\runners.py", line 118, in run return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\asyncio\base_events.py", line 653, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "C:\agent_new\test.py", line 12, in gather_miner_data miner_data = await miner.get_data() ^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\base.py", line 533, in get_data gathered_data = await self._get_data( ^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\base.py", line 498, in _get_data raise e File "C:\agent_new\local_libs\pyasic\miners\base.py", line 496, in _get_data miner_data[data_name] = await function(**args_to_send) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\miners\backends\cgminer.py", line 158, in _get_pools pool_url = PoolUrl.from_str(url) if url else None ^^^^^^^^^^^^^^^^^^^^^ File "C:\agent_new\local_libs\pyasic\data\pools.py", line 34, in from_str port = parsed_url.port ^^^^^^^^^^^^^^^ File "C:\Users\User1\AppData\Local\Programs\Python\Python311\Lib\urllib\parse.py", line 184, in port raise ValueError("Port out of range 0-65535") ValueError: Port out of range 0-65535

b-rowan commented 1 week ago

Lol. Maybe tell your techs not to use an invalid port like that, its much better to prefix it with an invalid schema, such as fail://stratum+tcp://....

I could fix that in pyasic I suppose, but its really not a pyasic issue. The issue here is that there is no way for me to know what the desired port is from that.

b-rowan commented 1 week ago

its much better to prefix it with an invalid schema, such as fail://stratum+tcp://....

Even this isnt correct now that I think about it, pyasic tries to parse the stratum type too, so from my side the best thing is just to prefix the actual path, EG stratum+tcp://hasnt-payed.their-pool.whatever:1234

Combinator78 commented 1 week ago

Okay, I'll instruct the technicians, thank you!