Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UBWA - subscribe_to_stream bug #374

Open
1 task done
benniebendiksen opened this issue Apr 14, 2024 · 16 comments
Open
1 task done

UBWA - subscribe_to_stream bug #374

benniebendiksen opened this issue Apr 14, 2024 · 16 comments
Assignees
Labels
bug Something isn't working will be realized

Comments

@benniebendiksen
Copy link

benniebendiksen commented Apr 14, 2024

Solution to Issue cannot be found in the documentation or other Issues and also occurs in the latest version of this library.

  • I checked the documentation and other Issues. I am using the latest version of this library.

Version of this library.

2.4.0

Hardware?

VPS or other cloud hosting

Operating System?

Linux

Python version?

Python3.12

Installed wheel files

unicorn_binance_rest_api-2.2.1.dist-info/INSTALLER
  unicorn_binance_rest_api-2.2.1.dist-info/LICENSE
  unicorn_binance_rest_api-2.2.1.dist-info/METADATA
  unicorn_binance_rest_api-2.2.1.dist-info/RECORD
  unicorn_binance_rest_api-2.2.1.dist-info/WHEEL
  unicorn_binance_rest_api-2.2.1.dist-info/top_level.txt
  unicorn_binance_rest_api/__init__.cpython-312-x86_64-linux-gnu.so
  unicorn_binance_rest_api/enums.cpython-312-x86_64-linux-gnu.so
  unicorn_binance_rest_api/exceptions.cpython-312-x86_64-linux-gnu.so
  unicorn_binance_rest_api/helpers.cpython-312-x86_64-linux-gnu.so
  unicorn_binance_rest_api/manager.cpython-312-x86_64-linux-gnu.so

Installed packages

# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                        main  
_openmp_mutex             5.1                       1_gnu  
aiohttp                   3.9.3                    pypi_0    pypi
aiosignal                 1.3.1                    pypi_0    pypi
aniso8601                 9.0.1                    pypi_0    pypi
appdirs                   1.4.4              pyhd3eb1b0_0  
attrs                     23.1.0          py312h06a4308_0  
automat                   20.2.0                     py_0  
bcrypt                    3.2.0           py312h5eee18b_1  
blas                      1.0                         mkl  
blinker                   1.7.0                    pypi_0    pypi
bottleneck                1.3.7           py312ha883a20_0  
brotli-python             1.0.9           py312h6a678d5_7  
bzip2                     1.0.8                h5eee18b_5  
ca-certificates           2024.3.11            h06a4308_0  
certifi                   2024.2.2        py312h06a4308_0  
cffi                      1.16.0          py312h5eee18b_0  
charset-normalizer        2.0.4              pyhd3eb1b0_0  
cheroot                   10.0.0                   pypi_0    pypi
click                     8.1.7                    pypi_0    pypi
colorama                  0.4.6                    pypi_0    pypi
constantly                23.10.4         py312h06a4308_0  
cryptography              42.0.5          py312hdda0065_0  
cython                    3.0.9                    pypi_0    pypi
dateparser                1.2.0                    pypi_0    pypi
expat                     2.5.0                h6a678d5_0  
flask                     3.0.2                    pypi_0    pypi
flask-restful             0.3.10                   pypi_0    pypi
frozenlist                1.4.1                    pypi_0    pypi
hyperlink                 21.0.0             pyhd3eb1b0_0  
idna                      3.4             py312h06a4308_0  
incremental               22.10.0            pyhd3eb1b0_0  
intel-openmp              2023.1.0         hdb19cb5_46306  
itsdangerous              2.1.2                    pypi_0    pypi
jaraco-functools          4.0.0                    pypi_0    pypi
jinja2                    3.1.3                    pypi_0    pypi
ld_impl_linux-64          2.38                 h1181459_1  
libffi                    3.4.4                h6a678d5_0  
libgcc-ng                 11.2.0               h1234567_1  
libgomp                   11.2.0               h1234567_1  
libstdcxx-ng              11.2.0               h1234567_1  
libuuid                   1.41.5               h5eee18b_0  
lucit-licensing-python    1.8.2                    pypi_0    pypi
markupsafe                2.1.5                    pypi_0    pypi
mkl                       2023.1.0         h213fc3f_46344  
mkl-service               2.4.0           py312h5eee18b_1  
mkl_fft                   1.3.8           py312h5eee18b_0  
mkl_random                1.2.4           py312hdb19cb5_0  
more-itertools            10.2.0                   pypi_0    pypi
multidict                 6.0.5                    pypi_0    pypi
ncurses                   6.4                  h6a678d5_0  
numexpr                   2.8.7           py312hf827012_0  
numpy                     1.26.4          py312hc5e2394_0  
numpy-base                1.26.4          py312h0da6c21_0  
openssl                   3.0.13               h7f8727e_0  
pandas                    2.1.1           py312h526ad5a_0    anaconda
pip                       23.3.1          py312h06a4308_0  
psutil                    5.9.8                    pypi_0    pypi
pyasn1                    0.4.8              pyhd3eb1b0_0  
pyasn1-modules            0.2.8                      py_0  
pycparser                 2.21               pyhd3eb1b0_0  
pycryptodome              3.20.0                   pypi_0    pypi
pyopenssl                 24.0.0          py312h06a4308_0  
pysocks                   1.7.1           py312h06a4308_0  
python                    3.12.2               h996f2a0_0  
python-binance            1.0.19                   pypi_0    pypi
python-dateutil           2.8.2              pyhd3eb1b0_0  
python-tzdata             2023.3             pyhd3eb1b0_0  
pytz                      2023.3.post1    py312h06a4308_0  
readline                  8.2                  h5eee18b_0  
regex                     2023.12.25               pypi_0    pypi
requests                  2.31.0          py312h06a4308_0    anaconda
service_identity          18.1.0             pyhd3eb1b0_1  
setuptools                68.2.2          py312h06a4308_0  
shared-memory-dict        0.7.2                    pypi_0    pypi
simplejson                3.19.2                   pypi_0    pypi
six                       1.16.0             pyhd3eb1b0_1  
sqlite                    3.41.2               h5eee18b_0  
tbb                       2021.8.0             hdb19cb5_0  
tk                        8.6.12               h1ccaba5_0  
twisted                   23.10.0         py312h06a4308_0  
typing_extensions         4.9.0           py312h06a4308_1  
tzdata                    2024a                h04d1e81_0  
tzlocal                   5.2                      pypi_0    pypi
ujson                     5.9.0                    pypi_0    pypi
unicorn-binance-rest-api  2.2.1                    pypi_0    pypi
unicorn-binance-websocket-api 2.4.0                    pypi_0    pypi
unicorn-fy                1.14.1                   pypi_0    pypi
urllib3                   1.26.18         py312h06a4308_0  
websocket-client          1.7.0                    pypi_0    pypi
websockets                11.0.3                   pypi_0    pypi
werkzeug                  3.0.1                    pypi_0    pypi
wheel                     0.41.2          py312h06a4308_0  
xz                        5.4.6                h5eee18b_0  
yarl                      1.9.4                    pypi_0    pypi
zlib                      1.2.13               h5eee18b_0  
zope                      1.0             py312h06a4308_1  
zope.interface            5.4.0           py312h5eee18b_0

Logging output

No response

Processing method?

process_stream_data

Used endpoint?

binance.com

Issue

ubwa_stream_subscribe_issue.py.log.zip
ubwa_stream_subscribe_issue.py.zip

periodic calls into subscribe_to_stream() returns a payload value of False, and the corresponding market does not undergo the subscription. This error happens periodically with no clear pattern as to when.

Print statements at the time of the first occurrence was as follows:

UNSUBSCRIBED btttrx, market count: 702
2024-04-13 23:30:16: FAILED TO SUBSCRIBE TO wintrx STREAM!!! Adding to self.unicorn_failed_market_subscriptions ...
Len of self.unicorn_failed_market_subscriptions: 1
UNSUBSCRIBED chzbnb, market count: 701

Log file simply shows that the coin in question, wintrx, was listed in the list of subscription markets, and subsequently, that the payload value was False.

@benniebendiksen benniebendiksen added the bug Something isn't working label Apr 14, 2024
@oliver-zehentleitner
Copy link
Member

Hi Pablo!

I know the code part that is responsible for this and I want to rework it because of the Binance Websocket API. I want to send directly via loop.create_task() to reduce the latency, probably I can fix your issue as well. I will report here as soon as you can try it again.

@benniebendiksen
Copy link
Author

benniebendiksen commented Apr 25, 2024

Hi Oliver,

Please forgive my delayed response. It is the busiest time of the academic semester for me.

The issue still exists as of today (version 2.4.0). From inspecting the ubwa manager class code statically, I think the issue of the False payload value may be localized here:

If this is correct then I am curious as to why calls into this split_payload() method from the "unsubscribe" method do not result in the returned value of False (I have only observed the issue with subscribe attempts).

Can you show me how calling into subscribe_to_stream() results in sending API requests via the established websocket connection? I cannot find the link between appending to self.stream_list[stream_id]['payload']—which is what I understand subscribe_to_stream() and unsubscribe_from_stream() to be doing (in the absence of this issue)—and the addition of a task to an event loop or some other mechanism to send an API request.

In any case, if self.stream_list[stream_id]['payload'] does not get appended to from within subscribe_to_stream() whenever the issue is encountered, then doesn't that mean that no request gets sent to the Binance Websocket API?

I can try to dynamically trace the issue when I have more time, if it would help, which would be after mid-May.

@oliver-zehentleitner
Copy link
Member

Good approach from you, I will test why the False occurs at all.

I first have to integrate the option of accessing the event loop directly and sending it via websocket, which is not yet available. Currently the payload is stored on a stack and another process picks it up from the stack in the loop and then sends it (not optimal ^^) In the future I would like to have this done directly via a separate asyncio task.

@oliver-zehentleitner
Copy link
Member

A legitimate reason for a False would be if max_subscriptions_per_stream is exceeded! Have you checked this? You should then find a "The limit of X has been exceeded!" in the logs and you can retrieve the current number of subscriptions:
https://unicorn-binance-websocket-api.docs.lucit.tech/unicorn_binance_websocket_api.html#unicorn_binance_websocket_api.manager.BinanceWebSocketApiManager.get_limit_of_subscriptions_per_stream
https://unicorn-binance-websocket-api.docs.lucit.tech/unicorn_binance_websocket_api.html#unicorn_binance_websocket_api.manager.BinanceWebSocketApiManager.get_number_of_subscriptions

Another reason would be the TypeError that is caught, here you should also find a log note!

I have now equipped all the functions involved with logging at the crucial points, if you activate debug level "ERROR", you will have all the relevant information in the log before the False is returned. So we can better investigate the reason.

@oliver-zehentleitner
Copy link
Member

Please test with version 2.6.0 and keep an eye on the max subscriptions!

@benniebendiksen
Copy link
Author

A legitimate reason for a False would be if max_subscriptions_per_stream is exceeded! Have you checked this? You should then find a "The limit of X has been exceeded!" in the logs and you can retrieve the current number of subscriptions: https://unicorn-binance-websocket-api.docs.lucit.tech/unicorn_binance_websocket_api.html#unicorn_binance_websocket_api.manager.BinanceWebSocketApiManager.get_limit_of_subscriptions_per_stream https://unicorn-binance-websocket-api.docs.lucit.tech/unicorn_binance_websocket_api.html#unicorn_binance_websocket_api.manager.BinanceWebSocketApiManager.get_number_of_subscriptions

Another reason would be the TypeError that is caught, here you should also find a log note!

I have now equipped all the functions involved with logging at the crucial points, if you activate debug level "ERROR", you will have all the relevant information in the log before the False is returned. So we can better investigate the reason.

Hi Oliver,

Thank you for looking into this. I keep an eye on the stream subscription count by printing the market count associated with the stream. Also, in the example script for this issue—ubwa_stream_subscribe_issue.py, we initialize markets to not exceed the subscription limit (I believe the value is 1024 markets), i.e., markets = markets[:subscribe_limit]. The subscribe_limit we grab from the method call indicated in your first provided web link. We then associate boolean values to these markets by sampling a uniform distribution and create the stream by subscribing to the markets associated with a value of True. We then iteratively unsubscribe from subscribed markets and subscribe to unsubscribed markets. So on average there will be 50% of the subscription number limit of markets subscribed to the stream, and never will there be more than the limit. Nevertheless, as an added measure, I will go ahead and use the get_number_of_subscriptions() method you reference with your second provided web-link. But my belief is that the problem isn't there.

The TypeError, to my understanding, used to result when the program would attempt to iterate the payload variable, thereby assuming it to be of an iterable type. Yet, as we know, that payload variable is actually a Bool at times when the bug shows up. In version 2.4.0 the TypeError stopped getting thrown because of the try-catch wrap you placed before the program attempts to iterate the payload.

As for a log note in either case, I will go ahead and run the example script until the bug appears, terminate the program, and inspect all logs in the generated log file.

Thank you for extending the logger writes around more points surrounding this problem. I'll go ahead and activate the "ERROR" level of the logger when undergoing my testing.

@benniebendiksen
Copy link
Author

Good approach from you, I will test why the False occurs at all.

I first have to integrate the option of accessing the event loop directly and sending it via websocket, which is not yet available. Currently the payload is stored on a stack and another process picks it up from the stack in the loop and then sends it (not optimal ^^) In the future I would like to have this done directly via a separate asyncio task.

Ah, thank you for the explanation. I agree that an immediate addition of a task to a running event loop, as a result of a subscription or unsubscription method call, would be optimal.

@benniebendiksen
Copy link
Author

Please test with version 2.6.0 and keep an eye on the max subscriptions!

Yes, I will definitely do so. Please allow me to do so either Tuesday or Wednesday of the upcoming week.

@oliver-zehentleitner
Copy link
Member

The limits are different "binance.com" supports 1024 while "binance.com-futures" only supports 200: https://github.com/LUCIT-Systems-and-Development/unicorn-binance-websocket-api/wiki/Binance-websocket-endpoint-configuration-overview

UBWA knows these limits and you can use get_limit_of_subscriptions_per_stream() to query the limits for the endpoint used and get_number_of_subscriptions() to calculate the free slots. Or you can make it even easier for yourself and use get_number_of_free_subscription_slots().

@oliver-zehentleitner
Copy link
Member

It would actually be useful to throw an exception as soon as too many subscriptions are made.

@benniebendiksen
Copy link
Author

It would actually be useful to throw an exception as soon as too many subscriptions are made.

Agreed. Good idea

@benniebendiksen
Copy link
Author

get_limit_of_subscriptions_per_stream() to query the limits for the endpoint used

I apologize, I should've specified. I was referring to "binance.com"; I've not tested for the issue in the futures markets. I'll make sure to keep calling get_number_of_free_subscription_slots() to verify that free slots remain available at times when the payload bug occurs.

@oliver-zehentleitner
Copy link
Member

Update: 2.7.0

Test file

from unicorn_binance_websocket_api import BinanceWebSocketApiManager, MaximumSubscriptionsExceeded

try:
    ubwa.subscribe_to_stream(stream_id=stream_id, channels=['kline_1m', 'depth20'])
except MaximumSubscriptionsExceeded as e:
    print(f"ERROR: {e}")

Please let me know if you still have problems or if I can close the issue.

@benniebendiksen
Copy link
Author

benniebendiksen commented May 16, 2024

ubwa_stream_subscribe_issue_2.py.log
ubwa_stream_subscribe_issue_2.py.zip

Hi Oliver,
Please note the log file and its corresponding test py file above. The test file utilizes both the "MaximumSubscriptionsExceeded" Exception and also calls the "get_number_of_free_subscription_slots(stream_id)" method when the bug is encountered.

I understand now why you desired to inspect the number of subscriptions to ensure we were not exceeding the maximum subscription number when encountering the error. Here is a mismatch for you regarding this topic:

Print statements from the client side indicate that we do not exceed the subscription limit (1024 for Binance.com CEX):
...
SUBSCRIBED arpausdt, market count: 703
SUBSCRIBED trxbusd, market count: 703
SUBSCRIBED eosbusd, market count: 703
SUBSCRIBED iotxusdt, market count: 703
UNSUBSCRIBED rlcusdt, market count: 702
2024-05-16 01:01:01: FAILED TO SUBSCRIBE TO mcousdt STREAM!!! Adding to self.unicorn_failed_market_subscriptions
Num free slots: 322 ...

Yet, inspecting the log file we see that:

2024-05-16 01:00:23,629 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!
2024-05-16 01:00:28,655 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!
2024-05-16 01:00:35,954 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!
2024-05-16 01:00:40,976 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!
2024-05-16 01:00:45,999 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!
2024-05-16 01:01:00,376 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiSocket.start_socket(a473242bad0d-11b3-4a3e-8141-92f4e838) - Received error message: {"error":{"code":4,"msg":"Too many subscriptions"},"id":1533}
2024-05-16 01:01:01,510 [ERROR ] 3720 139648872167040 ubwa_stream_subscribe_issue_2: BinanceWebSocketApiManager.split_payload() CEX result is None!
2024-05-16 01:01:01,511 [ERROR ] 3720 139648872167040 ubwa_stream_subscribe_issue_2: BinanceWebSocketApiManager.subscribe_to_stream(a473242bad0d-11b3-4a3e-8141-92f4e838) - error_msg: Payload is None!
2024-05-16 01:01:01,874 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.split_payload() CEX result is None!
2024-05-16 01:01:01,874 [ERROR ] 3720 139648502327040 events: BinanceWebSocketApiManager.subscribe_to_stream(a473242bad0d-11b3-4a3e-8141-92f4e838) - error_msg: Payload is None!

So something is happening that is causing a "Too many subscriptions" error to be received by the websocket. UBWA has no awareness of this over-subscribing as indicated by the fact that, in subscribe_to_stream(), even though the logger reports that payload is None, it never reports that the limit of 1024 subscriptions per stream has been exceeded; in other words—if self.stream_list[stream_id]['subscriptions'] > self.max_subscriptions_per_stream:—is not getting hit.

The payload value of None can be traced to the call to self.split_payload() here:

payload = self.split_payload(params, "SUBSCRIBE")

which receives a None returned value coming from here:

Since the logger reports a sequence of "BinanceWebSocketApiManager.send_with_stream(a473242bad0d-11b3-4a3e-8141-92f4e838 - Timeout exceeded!" errors before the "too many subscriptions" error, do you think that maybe self.stream_list[stream_id]['payload'] is getting overloaded from multiple calls into self.add_payload_to_stream() in the following

self.add_payload_to_stream(stream_id=stream_id, payload=item)

?

@oliver-zehentleitner
Copy link
Member

I dont think its "overloaded", i will investigate this, but give me some time

@benniebendiksen
Copy link
Author

Yes of course, thank you for your time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working will be realized
Projects
None yet
Development

No branches or pull requests

2 participants