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

Emitting with callback outside request context #1324

Closed
igoras1993 opened this issue Jul 10, 2020 · 4 comments
Closed

Emitting with callback outside request context #1324

igoras1993 opened this issue Jul 10, 2020 · 4 comments
Assignees
Labels

Comments

@igoras1993
Copy link
Contributor

Describe the bug
Okay, so I am using outside-of-context emit documented here. I am passing an acknowledge callback, like this:

socketio.emit(
    "event_name",
    "some data",
    namespace="some_namespace",
    room=sid,  # here sid is taken from another place, common way to emit to a single client
    callback=some_callable
)

This emit is done outside of request context. Documentation states that:

Callback functions can only be used when addressing an individual client.

Actually, I am doing that. I did not found a way to address individual client outside of request context other than passing session ID to the room argument.

As documentation stays:

This function can be used outside of a SocketIO event context, so it is appropriate to use when the server is the originator of an event, outside of any client context, such as in a regular HTTP request handler or a background task.

And I am doing that as well. Instead of emitting an event, an exception is raised telling that I am working outside of request context:

Connected in: e1960a60cea24c17b15a638d8a1335c7
[pid: 22569|app: 0|req: 1/1] 127.0.0.1 () {46 vars in 796 bytes} [Fri Jul 10 19:56:32 2020] GET /socket.io/?EIO=3&transport=polling&t=NCvtoVE => generated 119 bytes in 2 msecs (HTTP/1.1 200) 3 headers in 148 bytes (3 switches on core 799)
[pid: 22569|app: 0|req: 3/2] 127.0.0.1 () {46 vars in 870 bytes} [Fri Jul 10 19:56:32 2020] GET /socket.io/?EIO=3&transport=polling&t=NCvtoVl&sid=e1960a60cea24c17b15a638d8a1335c7 => generated 4 bytes in 9 msecs (HTTP/1.1 200) 2 headers in 99 bytes (3 switches on core 799)
Traceback (most recent call last):
  File "src/gevent/greenlet.py", line 766, in gevent._greenlet.Greenlet.run
  File "./run_wsgi.py", line 37, in emiter_task
    callback=ack_callback
  File "/home/igor/code/wed-box-bk/venv/lib/python3.6/site-packages/flask_socketio/__init__.py", line 412, in emit
    sid = getattr(flask.request, 'sid', None)
  File "/home/igor/code/wed-box-bk/venv/lib/python3.6/site-packages/werkzeug/local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/home/igor/code/wed-box-bk/venv/lib/python3.6/site-packages/werkzeug/local.py", line 306, in _get_current_object
    return self.__local()
  File "/home/igor/code/wed-box-bk/venv/lib/python3.6/site-packages/flask/globals.py", line 38, in _lookup_req_object
    raise RuntimeError(_request_ctx_err_msg)
RuntimeError: Working outside of request context.

This typically means that you attempted to use functionality that needed
an active HTTP request.  Consult the documentation on testing for
information about how to avoid this problem.
2020-07-10T17:56:52Z <Thread at 0x7f228fe69e48: emiter_task(sid='e1960a60cea24c17b15a638d8a1335c7')> failed with RuntimeError

[pid: 22569|app: 0|req: 4/3] 127.0.0.1 () {34 vars in 472 bytes} [Fri Jul 10 19:56:52 2020] GET /api/run/e1960a60cea24c17b15a638d8a1335c7 => generated 7 bytes in 3 msecs (HTTP/1.1 200) 2 headers in 70 bytes (3 switches on core 799)

Which clearly is caused by this line. I think that cause of this is that You actualy testing for Truth actual function instance and missed call to has_request_context() function a line above that.

To Reproduce
Here, some small piece of code to reproduce:

import flask, flask_socketio

app = flask.Flask(__name__)
io = flask_socketio.SocketIO(app=app)


# will supply sid from this endpoint
@app.route('/api/run/<sid>')
def run(sid):

    io.start_background_task(emiter_task, sid=sid)

    return flask.jsonify("Done")


@io.on("connect")
def connect():
    print(f"Connected in: {flask.request.sid}")


@io.on("disconnect") # , namespace="some_namespace")
def disconnect():
    print(f"Connected in: {flask.request.sid}")


def ack_callback():
    print("Acknowledged")


def emiter_task(sid):
    # will emit here, out of any context
    io.emit(
        "event_name",
        "some data",
        #namespace="some_namespace",
        room=sid,  # here sid is taken from another place, common way to emit to a single client
        callback=ack_callback
    )


print("Running app")

Expected behavior
No error are raised, event is emitted, acknowledge handlers are registered without pushing request context over.

Additional context
Example works fine when using this class:

class CustomSocketIO(flask_socketio.SocketIO):
    def emit(self, event, *args, **kwargs):
        namespace = kwargs.pop('namespace', '/')
        room = kwargs.pop('room', None)
        include_self = kwargs.pop('include_self', True)
        skip_sid = kwargs.pop('skip_sid', None)
        # added
        # callback_sid = kwargs.pop('callback_sid', None)
        if not include_self and not skip_sid:
            skip_sid = flask.request.sid
        callback = kwargs.pop('callback', None)
        if callback:
            # wrap the callback so that it sets app app and request contexts
            sid = None
            if has_request_context():
                sid = getattr(flask.request, 'sid', None)
            original_callback = callback

            def _callback_wrapper(*args):
                return self._handle_event(original_callback, None, namespace,
                                          sid, *args)

            if sid:
                # the callback wrapper above will install a request context
                # before invoking the original callback
                # we only use it if the emit was issued from a Socket.IO
                # populated request context (i.e. request.sid is defined)
                callback = _callback_wrapper
        self.server.emit(event, *args, namespace=namespace, room=room,
                         skip_sid=skip_sid, callback=callback, **kwargs)

BUT I AM NOT SURE IF THIS IS ENOUGH for everything to work ;) Correct me if I am wrong.

@miguelgrinberg
Copy link
Owner

You can only use a callback when you emit to a single client.

I did not found a way to address individual client outside of request context other than passing session ID to the room argument.

I don't understand this. What other way did you expect to find besides explicitly mentioning who is the client you want to emit to? That is really the only way.

@igoras1993
Copy link
Contributor Author

What other way did you expect to find besides explicitly mentioning who is the client you want to emit to? That is really the only way.

No other way. Did you read what I had written? Here I am saying that I AM doing this:

Actually, I am doing that.

Once more:

You have a missing call to has_request_context() in this line. Is this understandable? If not - run the code, you will see...

@miguelgrinberg
Copy link
Owner

Sorry, I did not interpret what you were saying correctly. When you said "I did not find a way... other than passing the sid" I assumed you were saying that passing the sid works, but you needed an alternative way w/o passing the sid. English is ambiguous sometimes.

The only problem is that I forgot the () next to has_request_context. Do you want to submit a PR?

@miguelgrinberg miguelgrinberg self-assigned this Jul 10, 2020
igoras1993 pushed a commit to igoras1993/Flask-SocketIO that referenced this issue Jul 11, 2020
 - Added proper call to has_request_context function
@igoras1993
Copy link
Contributor Author

Sorry for late response, I've created pull request to the master branch :)

@igoras1993 igoras1993 changed the title Emiting with callback outside request context Emitting with callback outside request context Jul 11, 2020
miguelgrinberg pushed a commit that referenced this issue Jul 11, 2020
- Added proper call to has_request_context function

Co-authored-by: igor_kantorski <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants