Project

General

Profile

How to WServer::Post - need example / help

Added by Michelle Dupuis over 6 years ago

I am trying to disable one widget based on the state of another (checkbox). Although it sounds simple, I've run into some segfaults that (from what I've read) relate to locks and the need to post a callback function.

I've tried to piece together some examples and have come up with this (the first function is called from a change lambda):

@void Base_SettingLineEdit::disable(const bool disabled) {

thisServer~~post(wApp~~>sessionId(), std::bind(post_disable, disabled));

}

void Base_SettingLineEdit::post_disable(const bool disabled) {

m_editField->setEnabled(!disabled);

wApp->triggerUpdate();

}@

But I can't get it to compile, and thought I might ask for some help:

1. Does the function that I post ("post_disable") have to be a static method?

  1. How do I find the current server value ("thisServer" above)? I can't find any examples of how to retrieve this.
  2. Is "post" necessary only when modifying the widgets (eg: setEnable), or also when reading the value/state of a widget?

Replies (17)

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

For #2, I found "Wt::WServer::instance()" but if there is only one instance of WServer, why bother retrieve it and pass it? (Am I misunderstanding this)

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

But retrieving "Wt::WServer::instance()" causes a segfault because session_ (in WApplication) is null. So still stuck on that one.

As for #1, I'm experimenting with:

server->post(wApp->sessionId(), std::bind(&Base_SettingLineEdit::post_disable, this, disabled));

but this might be contributing to the problem?

RE: How to WServer::Post - need example / help - Added by lm at over 6 years ago

I'm pretty sure that Wt::WServer::post is the wrong solution to whatever problem you were having. The only reason to use Wt::WServer::post to my knowledge is if you're on a different thread and want to call functions on a Wt widget. In that case, you'll want the "Wt" thread to run any code that modifies widgets (this is normal in every GUI toolkit I know).

In your case, though, it sounds like some checkbox is being ticked and should set the visibility of another widget. The event handler that is running when the checkbox value changes is already running on the WT thread, so just do what you need to do.

(By the way, when you need to post multiple lines of code, wrap it in HTML "pre" element.)

For instance,

void settings::checkboxChanged(bool checked)
{
    otherWidget->setHidden(checked);
}

Or whatever it should be. You said you were hitting segmentation faults with simpler code like this; are you able to reproduce that and perhaps post the pertinent parts of code?

RE: How to WServer::Post - need example / help - Added by Roel Standaert over 6 years ago

The only reason to use Wt::WServer::post to my knowledge is if you're on a different thread and want to call functions on a Wt widget

Also when you want to notify a different WApplication than the one you're using, or maybe just as a way to delay something if a WApplication posts to itself.

I indeed don't see how WServer::post() is applicable in this case.

You can indeed use WServer::instance() if there is only one WServer. You usually have one WServer, but nothing prevents you from having more than one in one process if that happens to be necessary.

You can also get the server from WEnvironment: https://www.webtoolkit.eu/wt/doc/reference/html/classWt_1_1WEnvironment.html#a710ab3dd56b6f1dfbf62e5ed040dd7a3

I don't know why you're getting a segfault, though. That should be fine:

WApplication *app = WApplication::instance();
app->environment().server()->post(app->sessionId(), std::bind(&Base_SettingLineEdit::post_disable, this, disabled));

The main risk there is that you need to be sure that this stays valid. That's not guaranteed, so you may want to use bindSafe: https://www.webtoolkit.eu/wt/doc/reference/html/classWt_1_1Core_1_1observable.html#a836dedaeb83d6002de00d6ac2a5b0315

You'll also need to make sure that server push is enabled with enableUpdates().

I don't know why you'd need post() for this, though.

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

OK - I'm starting to understand why the need to post, I neglected to mention something important:

I'm mixing Qt slots/signals with Wt. And the change event from the WCheckbox emits a Qt signal, and I am trying to change other WWidgets from the Qt slot. So that's definitely in another thread. (Qt event loop)

I'm trying to manage the overall form from an object 2 levels higher than the checkbox widget. I thought the right way to do that would be signals (since the WCheckBox can't see the other WWidgets I want to enable/disable)

Is this a bad design? Is the problem because I'm doing this with Qt? How can I have the form (a couple object layers higher) own control of enabling/disabling WWidgets?

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

To summarize, my WCheckbox's change event emits a Qt signal. The Qt slot calls the disable method below:

void Base_SettingLineEdit::disable(const bool disabled) {
    auto server = Wt::WServer::instance();
    server->post(wApp->sessionId(), std::bind(&Base_SettingLineEdit::post_disable, this, disabled));
}

void Base_SettingLineEdit::post_disable(const bool disabled) {
    m_editField->setEnabled(!disabled);
    wApp->triggerUpdate();
}

and the program crashes on the one line below trying to get the session id. GDB reports that 'this' is 0x0 so it segfaults. (I'm working in Qt creator in case that matters)

std::string WApplication::sessionId() const
{
  return session_->sessionId();
}

So since the widget is being modified outside the Wt thread that explains why I need to post. But not why in sessionId() 'this' is a nullptr.

I'm thinking that I'm doing something fundamentally wrong in my design. I would like my form object (2 levels higher in the object heirarchy) to enforce rules about enabling/disabling WWidgets, and the only way to notify a couple levels up in the heirarchy is signals (without using callbacks).

I was hoping to use Qt signals/slots because of comfort with them...but what's the best way to solve this?

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

And just to add, I confirmed both wapp and wqapp are 0x0 in my disable method:

    auto wqapp = Wt::WQApplication::instance();
    auto wapp = Wt::WApplication::instance();

in case that helps...

RE: How to WServer::Post - need example / help - Added by lm at over 6 years ago

That seems like a sane design. In some deeper composite widget there is a checkbox. When that checkbox is used by the user, the composite widget emits an event to higher widgets that can handle the event properly. This is fine.

Well, I don't know the ins and outs of when Wt::WApplication::instance should return nullptr, nor what kinds of extenuating circumstances exist in your application (with two loops), so you have some debugging to do :-)

I would begin by figuring out when Wt::WApplication::instance() returns nullptr. Speckle statements like std::cout << "from thread " << std::this_thread::get_id() << " app is " << Wt::WApplication::instance() << '\n'; throughout your code, especially in your Wt::WApplication constructor, in your Qt constructors, in some callbacks, etc. Maybe you'll figure out that you're looking for the Wt::WApplication instance before it's created? Or perhaps it's only available from certain threads (doesn't seem likely)? etc. Maybe also look at Wt::WEnvironment::server(), too?

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

I did as you suggested, and wapp is valid in the change lambda where I emit the Qt signal, but not in the Qt slot. (It's null in the slot)

I think I need someone from Emweb to offer some advice. Should Wt::WQApplication::instance() be visible in all threads?

Should I pass the instance value in my Qt signal? (Feels like I'm asking for trouble if I'm not supposed to act on the WQApplication outside the Wt thread)

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

I found a discussion from 9 years ago with this exact problem:

[[[https://witty-interest.narkive.com/M4Ipri4S/wt-interest-can-t-propagate-server-initiated-updates-from-qt-thread]]]

If I understand correctly there was a fix in Wt..but after 9 years I'm guessing that died. I also found this post:

[[[https://redmine.emweb.be/boards/2/topics/12966]]]

of someone also trying to explain why instance() returns null...but no response to that question. I'm not really any closer to an answer, still wondering if my design is wrong, or I'm missing something basic.

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

Tracing through the Wt code it seems that application instance is null because:

Application::Instance() calls

WebSession::instance() calls

WebSession::Handler::instance()

which returns the threadhandler_ which is null. I see method attachThreadToHandler in WebSession.C can set this value, but no idea if/when I should call that. Getting deeper into the Wt magic that I hoped to avoid.

RE: How to WServer::Post - need example / help - Added by lm at over 6 years ago

Something in one of those posts to which you linked reminded me of something. The docs on Wt::WServer::post say, "with WApplication::instance() pointing to the correct application". That suggests to me that Wt::WApplication::instance may not be pointing to the correct location without Wt::WServer::post. You need to call post, and only access the Wt::WApplication inside your post function.

You said "But retrieving "Wt::WServer::instance()\" causes a segfault because session_ (in WApplication) is null\", but session_ in WApplication shouldn't matter when calling Wt::WServer::instance. The latter is a static member function, and it has nothing to do with WApplication. As long as a Wt::WServer has been created by that point, Wt::WServer::instance() should return it (or one of them if more than one?).

In my application (in which I also have another event loop), I create my Wt::WServer in main and pass pointers to it for objects that need access to post. I also Wt::WServer::instance in other parts of the code.

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

So you are saving your Wt::WServer point created in main - but my server variable (below) actually looks valid.

In my case wqapp is null (causing call to sessionId to crash). I thought about saving wqapp to a global when I first create my Wt::WQApplication descendant, but it seems risky. I've read many posts about why Wt::WApplication::instance() might return null.

auto server = Wt::WServer::instance();
auto wqapp = Wt::WQApplication::instance();
server->post(wqapp->sessionId(), std::bind(&Base_SettingLineEdit::post_disable, this, disabled));

Did I misunderstand your post? You said I should only access Wt::WApplication inside my post function, but I can't post that function without Wt:WApplication::instance().

RE: How to WServer::Post - need example / help - Added by Michelle Dupuis over 6 years ago

Ok - I got it working! Your post gave me an idea.

I grabbed the sessionID from within the lambda (in the Wt thread), and added that to my Qt signal. Then in my Qt slot I post'ed the function using the passed sessionID. (So I didn't need the WApplication instance).

And it works :) I hope this approach doesn't come back to bite me later. But it makes sense that given the possibility of multiple sessions on my single page app, that Wt would need to know on which session to disable the widget.

Thanks for the help lm!

RE: How to WServer::Post - need example / help - Added by lm at over 6 years ago

You were right, it's a little more complicated. From my Wt::WApplication constructor, I send sessionId() out... somewhere else (where is not pertinent just now!). That something else sends the session ID back through the other message loop, and I use it to call Wt::WServer::post. Sounds like you have the same thing going on. I suppose the only issue would be if the session is not valid anymore when you try to call post?

RE: How to WServer::Post - need example / help - Added by Roel Standaert over 6 years ago

When you use post() with a session that's not valid anymore, it won't do anything. This is why post() also takes a fallbackFunction argument, to allow you to do something else if the session doesn't exist (anymore).

RE: How to WServer::Post - need example / help - Added by Roel Standaert over 6 years ago

I'm mixing Qt slots/signals with Wt. And the change event from the WCheckbox emits a Qt signal, and I am trying to change other WWidgets from the Qt slot. So that's definitely in another thread. (Qt event loop)

If you're using a WQApplication, that should not normally move to another thread. Every WQApplication will get its own QThread, and you should make sure that if you're using QObjects, that their affinity is set to that thread.

If you do for some reason want to trigger a slot on another thread, then indeed in that thread you don't have the UpdateLock, or a current WApplication instance, and you should use post() if you want to modify that WApplication, but you indeed would have to know the session id at that point, because WApplication::instance() is not going to work.

If you're just using Qt slots and signals because you're comfortable with them, and you don't have another reason, you're just going to be making things more complicated than they need to be.

    (1-17/17)