Delayed response using QJsonRpcService

177 views
Skip to first unread message

kirill...@gmail.com

unread,
Dec 2, 2013, 8:24:08 AM12/2/13
to qjsonrpc-d...@googlegroups.com
Hello

Is there an ability to delay response when I use QJsonRpcService?
Here is an example:
class MyService : public QJsonRpcService
{
...

public slots:
void mySlot()
{
//can I get the request id here?
//I use a lambda here, because the task I am going to do is async
//and I want to create a response from the lambda
//is this possible?
//I only found how to get the sender socket - senderSocket()
}
}

Matt Broadstone

unread,
Dec 2, 2013, 10:22:18 AM12/2/13
to Kirill Bukaev, qjsonrpc-d...@googlegroups.com
On Mon, Dec 2, 2013 at 8:24 AM, <kirill...@gmail.com> wrote:
Hello

Is there an ability to delay response when I use QJsonRpcService?

Aside from putting a sleep in the slot, there is currently no way to do this. This is a design limitation in QJsonRpc currently that would take a bit of work and thought to overcome. Providing the request id is not necessarily the best way to handle the problem imho, if you really want to do everything by hand this way then its probably better to just implement a new server class.

Instead, I think opting for the approach KDSoap uses might be better: since each QJsonRpcService is pretty light weight, a service object can be cloned to a number of threads (in the servers threadpool), so blocking operations won't effectively block the server from accepting new connections. 

That's just one idea, I am totally willing to discuss alternatives that people propose, I just haven't had a use case for this yet.
 
Here is an example:
class MyService : public QJsonRpcService
{
 ...

public slots:
  void mySlot()
  {
    //can I get the request id here?
    //I use a lambda here, because the task I am going to do is async
    //and I want to create a response from the lambda
    //is this possible?
    //I only found how to get the sender socket - senderSocket()
  }
}


Matt
 
--
You received this message because you are subscribed to the Google Groups "qjsonrpc-development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to qjsonrpc-develop...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Matt Broadstone

unread,
Oct 31, 2014, 6:56:53 PM10/31/14
to qjsonrpc-d...@googlegroups.com, kirill...@gmail.com
Hi,
Just wanted to let you know with the latest commit I have finally added support for a delayed response. If you're still using the project, or interested, I would love your feedback on the implementation!

Thanks,
Matt

Luca Cavalli

unread,
Jan 12, 2015, 3:26:33 AM1/12/15
to qjsonrpc-d...@googlegroups.com, kirill...@gmail.com
Hi Matt,

I'm using delayed responses right now on my project.

I have methods called via QJsonRPC, some of them require long execution times, thus I manually handle the response:

bool MyRpcService::longActionRequest(void)
{
    bool res = 0;
    QJsonRpcServiceRequest request;

    beginDelayedResponse();
    request = currentRequest();

    if (m_engine->metaObject()->indexOfMethod(QMetaObject::normalizedSignature("longAction()").constData()) != -1) {
        QMetaObject::invokeMethod(m_engine, "longAction", Qt::AutoConnection,
                                  Q_RETURN_ARG(bool, res));
    }

    request.respond(res);

    return (res);
}


and this is the implementation:

bool MyEngine::longAction()
{
    QEventLoop loop;

    QTimer::singleShot(5000, &loop, SLOT(quit()));
    loop.exec();

    return (true);
}


So fa, so good. The long request takes its time and the server keeps answering other requests in the meanwhile.

The problem comes on the very next request following the beginDelayesResponse-ed one which doesn't send any reply back.

Those are the logs:

Server
** Blocking request of long method
received( QJsonRpcSocket(0x3aa850) ):  "{"id":1,"jsonrpc":"2.0","method":"MyService.longRequest"}"
sending( QJsonRpcSocket(0x3aa850) ):  "{"id":1,"jsonrpc":"2.0","result":true}"
** Non-blocking request of long method
received( QJsonRpcSocket(0x3aa850) ):  "{"id":2,"jsonrpc":"2.0","method":"MyService.longRequest"}"
** Missing reply of following request (id: 3)
received( QJsonRpcSocket(0x3aa850) ):  "{"id":3,"jsonrpc":"2.0","method":"MyService.normalRequest","params":["Sample text"]}"
received( QJsonRpcSocket(0x3aa850) ):  "{"id":4,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
sending( QJsonRpcSocket(0x3aa850) ):  "{"id":4,"jsonrpc":"2.0","result":true}"
received( QJsonRpcSocket(0x3aa850) ):  "{"id":5,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
sending( QJsonRpcSocket(0x3aa850) ):  "{"id":5,"jsonrpc":"2.0","result":true}"
received( QJsonRpcSocket(0x3aa850) ):  "{"id":6,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
sending( QJsonRpcSocket(0x3aa850) ):  "{"id":6,"jsonrpc":"2.0","result":true}"
sending( QJsonRpcSocket(0x3aa850) ):  "{"id":2,"jsonrpc":"2.0","result":true}"


Client
** Blocking request of long method
sending( QJsonRpcSocket(0x480a50) ):  "{"id":1,"jsonrpc":"2.0","method":"MyService.longRequest"}"
received( QJsonRpcSocket(0x480a50) ):  "{"id":1,"jsonrpc":"2.0","result":true}"
** Non-blocking request of long method
sending( QJsonRpcSocket(0x480a50) ):  "{"id":2,"jsonrpc":"2.0","method":"MyService.longRequest"}"
** Missing reply of following request (id: 3)
sending( QJsonRpcSocket(0x480a50) ):  "{"id":3,"jsonrpc":"2.0","method":"MyService.normalRequest","params":["Sample text"]}"
sending( QJsonRpcSocket(0x480a50) ):  "{"id":4,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
sending( QJsonRpcSocket(0x480a50) ):  "{"id":5,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
sending( QJsonRpcSocket(0x480a50) ):  "{"id":6,"jsonrpc":"2.0","method":"MyService.otherRequest"}"
received( QJsonRpcSocket(0x480a50) ):  "{"id":4,"jsonrpc":"2.0","result":true}"
received( QJsonRpcSocket(0x480a50) ):  "{"id":5,"jsonrpc":"2.0","result":true}"
received( QJsonRpcSocket(0x480a50) ):  "{"id":6,"jsonrpc":"2.0","result":true}"
received( QJsonRpcSocket(0x480a50) ):  "{"id":2,"jsonrpc":"2.0","result":true}"



Thanks for your help,

Luca

Matt Broadstone

unread,
Jan 12, 2015, 6:02:15 AM1/12/15
to qjsonrpc-d...@googlegroups.com

On Monday, January 12, 2015 at 3:26:33 AM UTC-5, Luca Cavalli wrote:
Hi Matt,

I'm using delayed responses right now on my project.

I have methods called via QJsonRPC, some of them require long execution times, thus I manually handle the response:


Okay, great! That's was the intended use of the delayed responses

 
bool MyRpcService::longActionRequest(void)
{
    bool res = 0;
    QJsonRpcServiceRequest request;

    beginDelayedResponse();
    request = currentRequest();

    if (m_engine->metaObject()->indexOfMethod(QMetaObject::normalizedSignature("longAction()").constData()) != -1) {
        QMetaObject::invokeMethod(m_engine, "longAction", Qt::AutoConnection,
                                  Q_RETURN_ARG(bool, res));
    }

    request.respond(res);

    return (res);
}



This seems to be your problem right here, you are still blocking in this code right here. The intended use of the delayedResponse request object is to be [passed off/stored in] a location where you can retrieve it after the long operation has completed. I know documentation on this project is pretty lacking, but there are some examples of using this feature in the tests themselves. There are two main ways I've been using it in my projects:

1) Pass the request object to a QRunnable. I prefer this approach because it nicely encapsulates the request with the long task, and makes for a pretty easy-to-follow flow. It is also pretty explicit about thread affinity, and can take advantage of the QThreadPool:

class MyTask : public QRunnable
{
public:
    MyTask(const QJsonRpcServiceRequest &request)
        : m_request(request)
    {
    }

    virtual void run() {
        // do some long task
        m_request.respond(someReturnValue);
    }

private:
    QJsonRpcServiceRequest m_request;

};

Then you would have this as a service:

class MyService : public QJsonRpcService
{
    Q_OBJECT
public:
    MyService(QObject *parent = 0)
        : QJsonRpcService(parent)
    {
    }

public Q_SLOTS:
    void someAction() {
        beginDelayedResponse();
        QJsonRpcServiceRequest request = currentRequest();
        QThreadPool::globalInstance()->start(new MyTask(request));
    }
};


2) You can store your request and refer to it in a slot. This is somewhat trickier if you have many of the same type of action coming in, since you need to some how "key" the request:

class MyService : public QJsonRpcService
{
    Q_OBJECT
public:
    MyService(QObject *parent = 0)
        : QJsonRpcService(parent)
    {
    }

public Q_SLOT:
    void someAction() {
        beginDelayedResponse();

        QProcess *process = new QProcess(this);
        connect(process, SIGNAL(finished()), this, SLOT(longTaskFinished()));
        process->start("someLongRunningProcessName", QStringList() << "with" << "arguments");   
        m_longTasks.insert(process, currentRequest());
    }

private Q_SLOTS:
    void longTaskFinished() {
        QProcess *process = qobject_cast<QProcess*>(sender());
        if (!process || !m_longTasks.contains(process)) {
           // something went wrong
           return;
        }

        QJsonRpcServiceRequest request = m_longTasks.take(process);
        request->respond(process->readAllStandardOutput());
        process->deleteLater();
    }

private:
    QHash<QProcess*, QJsonRpcServiceRequest> m_longTasks;

};


NOTE: I just wrote all of this off the cuff and did not actually compile it. It probably has errors, but the main idea is there.

Hope this helps!
Matt

Luca Cavalli

unread,
Jan 12, 2015, 6:21:21 AM1/12/15
to qjsonrpc-d...@googlegroups.com, kirill...@gmail.com
Hi Matt,

thanks! I choose your solution #1. Fits perfectly and solved the problem.

I still have some issues related to threads management, but nothing related to QJsonRPC.


Thanks again for your help.

Best wishes,

Luca
Reply all
Reply to author
Forward
0 new messages