Wt unexpected logout when clicking too fast
Added by Fil liF about 4 years ago
Hi,
I'm new to Wt and I'm experimenting with a small site inspired by the Hangman example.
I have two pages shown in the Wt::WStackedWidget.
One of them shows items of a database table but they are divided in pages in order to load only some items at a time.
So there is the Wt::WTableView plus two Wt::WPushButton to navigate to next/previous page.
When I click very fast and many times on theese buttons the page logout and I have to login again.
It may sound like excessive server load that rejects the client by logging it out but if I do the same on other unrelated buttons it doesn't happen.
So I think the problem is in the slot connected with clicked() signal of theese 2 buttons.
Here is the code:
Model header:
#ifndef SEARCHITEMMODEL_H
#define SEARCHITEMMODEL_H
#include <Wt/WAbstractTableModel.h>
#include <Wt/Dbo/Types.h>
#include <Wt/WGlobal.h>
namespace dbo = Wt::Dbo;
class Session;
class ExchangeableItem;
namespace Wt {
class WTimer;
}
class CustomModel : public Wt::WAbstractTableModel
{
public:
enum Columns
{
Tipologia = 0,
Colore,
Taglia,
Utente,
NCols
};
static constexpr int ItemsForPage = 10;
CustomModel(Session *s);
~CustomModel();
virtual int rowCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const override;
virtual int columnCount(const Wt::WModelIndex& parent = Wt::WModelIndex()) const override;
virtual Wt::cpp17::any data(const Wt::WModelIndex& index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const override;
virtual Wt::cpp17::any headerData(int section,
Wt::Orientation orientation = Wt::Orientation::Horizontal,
Wt::ItemDataRole role = Wt::ItemDataRole::Display) const override;
void recalcSize();
void loadPage(int page);
inline Wt::Signal<>& pageChanged() { return pageChanged_; }
inline int currentPage() const { return curPage; }
inline int pageCount() const { return pageCount_; }
private:
Session *session;
Wt::WTimer *clearIterTimer;
typedef dbo::collection<dbo::ptr<ExchangeableItem>> Items;
Items items;
Items::iterator cached_iter;
int64_t cached_row;
void clearCachedIter();
std::chrono::steady_clock::time_point lastIterAccessed;
int64_t totalItemsCount;
int pageCount_;
int curPage;
Wt::Signal<> pageChanged_;
};
#endif // SEARCHITEMMODEL_H
Model implementation:
#include "searchitemmodel.h"
#include "session.h"
#include "exchangableitem.h"
#include <Wt/WTimer.h>
#include <cmath>
CustomModel::CustomModel(Session *s) :
WAbstractTableModel(),
session(s),
clearIterTimer(nullptr),
items(),
cached_iter(items.end()),
cached_row(-1),
totalItemsCount(-1),
pageCount_(-1),
curPage(-1)
{
}
CustomModel::~CustomModel()
{
if(clearIterTimer)
{
delete clearIterTimer;
clearIterTimer = nullptr;
}
}
int CustomModel::rowCount(const Wt::WModelIndex &parent) const
{
return parent.isValid() ? 0 : items.size();
}
int CustomModel::columnCount(const Wt::WModelIndex &parent) const
{
return parent.isValid() ? 0 : NCols;
}
Wt::cpp17::any CustomModel::data(const Wt::WModelIndex &index, Wt::ItemDataRole role) const
{
Wt::cpp17::any ret;
if (role != Wt::ItemDataRole::Display || index.row() >= int64_t(items.size()))
return ret;
int row = index.row();
dbo::Transaction transaction(*session);
dbo::ptr<ExchangeableItem> ptr;
//NOTE: dbo::collection<> allows only forward iterators
//This means that every time we should start from begin() (row 0)
//and advance in a loop until we arrive to requested row (index.row())
//Instead to provide better performance we cache the last iterator:
//If quering the same row but different role/column we already have the iterator
//If quering next row we just have to advance by 1
//If quering for the first time or a row before cached then slow path of begin() and std::advance()
CustomModel *that = const_cast<CustomModel *>(this);
if(cached_row != -1 && cached_row == row - 1)
{
that->cached_iter++;
that->cached_row++;
}
else
{
if(cached_row == -1 || cached_row > row)
{
that->cached_iter = that->items.begin();
that->cached_row = 0;
}
if(row > cached_row)
{
std::advance(that->cached_iter, row - cached_row);
that->cached_row = row;
}
}
ptr = *that->cached_iter;
if(ptr)
{
switch (index.column())
{
case Tipologia:
ret = ptr->tipologia;
break;
case Colore:
ret = ptr->colore;
break;
case Taglia:
ret = ptr->taglia;
break;
case Utente:
if(ptr->user)
ret = ptr->user->getUserName();
break;
}
}
transaction.commit();
if(!clearIterTimer)
{
that->clearIterTimer = new Wt::WTimer();
that->clearIterTimer->setInterval(std::chrono::milliseconds(500));
that->clearIterTimer->timeout().connect(that, &CustomModel::clearCachedIter);
that->clearIterTimer->start();
}
that->lastIterAccessed = std::chrono::steady_clock::now();
return ret;
}
Wt::cpp17::any CustomModel::headerData(int section, Wt::Orientation orientation, Wt::ItemDataRole role) const
{
if (role == Wt::ItemDataRole::Display)
{
if (orientation == Wt::Orientation::Horizontal)
{
switch (section)
{
case Tipologia:
return Wt::WString("Tipologia");
case Colore:
return Wt::WString("Colore");
case Taglia:
return Wt::WString("Taglia");
case Utente:
return Wt::WString("Utente");
}
}
else
{
return curPage * ItemsForPage + section;
}
}
return Wt::cpp17::any();
}
void CustomModel::recalcSize()
{
dbo::Transaction transaction(*session);
totalItemsCount = session->query<int64_t, Wt::Dbo::DirectBinding>("SELECT COUNT() FROM items");
transaction.commit();
pageCount_ = std::ceil(double(totalItemsCount) / ItemsForPage);
pageChanged_.emit();
}
void CustomModel::loadPage(int page)
{
if(page == curPage || page >= pageCount_ || page < 0)
return; //Already loaded or invalid
int offset = page * ItemsForPage;
layoutAboutToBeChanged().emit();
dbo::Transaction transaction(*session);
items = session->find<ExchangeableItem>().orderBy("type").offset(offset).limit(ItemsForPage);
int size = items.size(); //Trigger size calculation
transaction.commit();
std::cerr << std::endl << std::endl
<< "LOADING PAGE " << page << " OF " << pageCount_ << " ITEMS " << size << " OF " << totalItemsCount
<< std::endl << std::endl;
cached_row = -1;
cached_iter = items.end();
curPage = page;
layoutChanged().emit();
pageChanged_.emit();
}
void CustomModel::clearCachedIter()
{
if(std::chrono::steady_clock::now() - lastIterAccessed < std::chrono::milliseconds(500))
return; //If last requset (e.g call to data() ) was too recent, wait next timeout
cached_row = -1;
cached_iter = items.end();
delete clearIterTimer;
clearIterTimer = nullptr;
}
Widget header:
#ifndef SEARCHITEMWIDGET_H
#define SEARCHITEMWIDGET_H
#include <Wt/WContainerWidget.h>
class Session;
class CustomModel;
namespace Wt {
class WPushButton;
class WLineEdit;
class WText;
class WSpinBox;
}
class SearchItemWidget : public Wt::WContainerWidget
{
public:
SearchItemWidget(Session *s);
void prevPage();
void nextPage();
private:
Session *session;
Wt::WText *infoLabel;
Wt::WLineEdit *searchEdit;
Wt::WPushButton *prevPageBut;
Wt::WPushButton *nextPageBut;
Wt::WPushButton *goToPageBut;
Wt::WSpinBox *goToPageSpin;
std::shared_ptr<CustomModel> model;
void handlePageChanged();
void goToPage();
};
#endif // SEARCHITEMWIDGET_H
Widget implementation:
#include "searchitemwidget.h"
#include "session.h"
#include <Wt/WText.h>
#include <Wt/WLineEdit.h>
#include <Wt/WSpinBox.h>
#include <Wt/WPushButton.h>
#include <Wt/WTableView.h>
#include <Wt/WGridLayout.h>
#include "searchitemmodel.h"
SearchItemWidget::SearchItemWidget(Session *s) :
session(s)
{
Wt::WGridLayout *lay = setLayout(std::make_unique<Wt::WGridLayout>());
lay->addWidget(std::make_unique<Wt::WText>("Work in progress... Items"), 0, 0);
searchEdit = lay->addWidget(std::make_unique<Wt::WLineEdit>(), 1, 0, 1, 5);
searchEdit->setPlaceholderText("Search for items here...");
infoLabel = lay->addWidget(std::make_unique<Wt::WText>(), 2, 0);
prevPageBut = lay->addWidget(std::make_unique<Wt::WPushButton>("Prev Page"), 2, 1);
nextPageBut = lay->addWidget(std::make_unique<Wt::WPushButton>("Next Page"), 2, 2);
goToPageSpin = lay->addWidget(std::make_unique<Wt::WSpinBox>(), 2, 3);
goToPageBut = lay->addWidget(std::make_unique<Wt::WPushButton>("Go"), 2, 4);
goToPageSpin->setMinimum(1);
Wt::WTableView *view = lay->addWidget(std::make_unique<Wt::WTableView>(), 3, 0, 1, 5);
view->setOverflow(Wt::Overflow::Scroll);
model = std::make_shared<CustomModel>(session);
view->setModel(model);
model->pageChanged().connect(this, &SearchItemWidget::handlePageChanged);
prevPageBut->clicked().connect(this, &SearchItemWidget::prevPage);
nextPageBut->clicked().connect(this, &SearchItemWidget::nextPage);
goToPageBut->clicked().connect(this, &SearchItemWidget::goToPage);
model->recalcSize();
model->loadPage(0);
}
void SearchItemWidget::handlePageChanged()
{
int curPage = model->currentPage();
int pageCount = model->pageCount();
goToPageSpin->setMaximum(pageCount);
goToPageSpin->setValue(curPage + 1);
prevPageBut->setEnabled(curPage > 0);
nextPageBut->setEnabled(curPage < pageCount - 1);
infoLabel->setText(Wt::WString("Page {1} / {2}")
.arg(curPage + 1)
.arg(pageCount));
}
void SearchItemWidget::prevPage()
{
model->loadPage(model->currentPage() - 1);
}
void SearchItemWidget::nextPage()
{
model->loadPage(model->currentPage() + 1);
}
void SearchItemWidget::goToPage()
{
int page = goToPageSpin->value() - 1;
model->loadPage(page);
}
Thank you in advance!
Replies (3)
RE: Wt unexpected logout when clicking too fast - Added by Roel Standaert about 4 years ago
What do you see in Wt's logs? Maybe there's an exception or some other error being logged?
RE: Wt unexpected logout when clicking too fast - Added by Fil liF about 4 years ago
There is an SQLite exception but that's normal because it tries to create again tables (database already exist) and it's catch'ed like in hangman example.
I noticed though that it happens twice: when I open a new Firefox tab and navigate to server address a new Session is created in AuthApplication constructor and ties to create tables leading to exception.
But when I login it happens again: it seems the AuthApplication is constructed again leading to the second exception.
Theese exception shouldn't be related to the problem, the database works fine and I can see the data correctly in the Firefox page.
There cant't find other exceptions in the log but there are often messages about expired timeouts, maybe there is a timeout in session login? If I don't click frenetically on those buttons I can stay logged in for quite a long time.
RE: Wt unexpected logout when clicking too fast - Added by Fil liF about 4 years ago
Attached the log file.
wt_site_log.txt (53.2 KB) wt_site_log.txt |