Project

General

Profile

How to quit all other session when a new one is established

Added by Eu Doxos almost 5 years ago

Hi everybody,

my app should have exclusive access to hardware resources (i2c sensors and such), for that reason I would like to terminate all previous sessions whenever a new one is established (not the other way, to refuse new sessions, that would be potentially confusing).

What I tried is to handle this in createApplication (I am using the wthttp server) in this way (not functional):

Wt::WApplication *createApplication(const Wt::WEnvironment& env)
{
    // create new session, but terminate all other sessions
    auto ret=new WooCamCxxApp(env);
    auto sessions=ret->environment().server()->sessions();
    for(const auto& sess: sessions){
        if(sess.sessionId==ret->sessionId()) continue; // keep this one
        // something like this for other sessions??
        ret->environment().server()->post(sess.sessionId,std::bind(&Wt::WApplication::quit));
    }
    return ret;
}

Is it possible to get WApplication pointer from sessionId? Perhaps there is a better way how to do this?

Thanks!


Replies (5)

RE: How to quit all other session when a new one is established - Added by Eu Doxos almost 5 years ago

I maintain list of app instances in a global variable, it seems to work. Any ideas for something more elegant still welcome...

class MyApp;
std::list<MyApp*> globalAppList;
std::mutex globalAppListMutex;

class MyApp: public Wt::WApplication{
   public:
   MyApp(const Wt::WEnvironment& env);
   ~MyApp() {
      std::lock_guard<std::mutex> lock(globalAppListMutex);
      globalAppList.remove_if([this](const MyApp* w){ return w==this; });
   };
   /* ... */
};

Wt::WApplication *createApplication(const Wt::WEnvironment& env)
{
   auto ret=new MyApp(env);
   std::lock_guard<std::mutex> lock(globalAppListMutex);
   for(auto app: globalAppList){
      Wt::WApplication::UpdateLock lock2(app);
      auto d=new Wt::WDialog("Session terminated",app->root());
      new Wt::WText("<p>Page was stopped since it was opened somewhere else.<br />You can reload this page,<br />which will stop the other instances.</p>",d->contents());
      d->show();
      app->quit();
   }
   globalAppList.push_back(ret);
   return ret;
}

RE: How to quit all other session when a new one is established - Added by Wim Dumon almost 5 years ago

Hey,

WApplication::quit() merely registers that the application should be quited. Since it's called asynchronously in this case, you have to use Wt's server push mechanism to cause a rendering of the application to happen. This will alert the web browser that the application is quited, and the WApplication object will be destroyed. Note that the deletion of WApplication would happen asynchronously to calling WApplication::triggerUpdate().

Your second code example looks dangerous to me. I don't think grabbing the update lock of another application from within an event loop is ok. It was intended to be used from threads that are not bound to a session/WApplication. Therefore, it looks to me that your second application works rather by accident than by design.

Best regards,

Wim.

RE: How to quit all other session when a new one is established - Added by Eu Doxos almost 5 years ago

Hi Wim,

thanks for the reply.

The UpdateLock documentation says "You can use this lock to manipulate widgets outside of the event loop." (https://www.webtoolkit.eu/wt/wt3/doc/reference/html/classWt_1_1WApplication_1_1UpdateLock.html#details) plus I was following examples/feature/serverpush/ServerPush.C, which does just that, as far as I can see.

Since you are from the emweb crew, I assume you have quite some knowledge of the internals... If the second case works only by accident, could you propose some better solution for session management?

Thanks!

RE: How to quit all other session when a new one is established - Added by Eu Doxos almost 5 years ago

PS I am using version 3.3.6, that's also where the example comes from.

RE: How to quit all other session when a new one is established - Added by Wim Dumon almost 5 years ago

Hello Eu,

The serverpush example, at least the Wt 4 version that I'm looking at now (and I'd be surprised if this is different in 3.3.6), is indeed grabbing the update lock from outside of the event loop, since it is grabbed in a thread that has no relation to Wt. That is correct use of UpdateLock.

In your code, you're grabbing the update lock for a different session, from within the event loop of your own session (admitedly, it can be debated if the createApplication() function is already part of what happens in the event loop).

Why this is a problem: Wt uses thread local storage to store what WSession/WApplication the current thread is serving. As part of grabbing an update lock, Wt will set that TLS pointer to the session whose lock is grabbed. This mechanism is not a stack: the old TLS will not be restored when the lock is released. So after releasing the lock, you're in an unexpected and unsupported state.

I would implement this by invoking WServer::postAll() with a function that checks if the sender is the newly created WApplication object, and terminate if that is the case. This is the idea:

class MyApp: public Wt::WApplication{
   public:
   MyApp(const Wt::WEnvironment& env);
   void terminate(MyApp *newlyCreated) {
      if(wApp == newlyCreated)
        return;
      quit();
      triggerUpdate();
   };
   /* ... */
};

Wt::WApplication *createApplication(const Wt::WEnvironment& env)
{
   auto ret=new MyApp(env);
   ret->enableUpdates();
   WServer.postAll(std::bind(MyApp::terminate, _1, ret));
   return ret;
}

Best regards,

Wim.

    (1-5/5)