Commit 3529c7b2 authored by bbguimaraes's avatar bbguimaraes
Browse files

transactions

parent 377e1b40
......@@ -11,3 +11,4 @@ for details/demos/screenshots.
with graphics and network communication.
- sudoku: Sudoku solver library/GUI using genetic algorithms.
- tablut: ANSI C / ncurses implementation of the game Tablut.
- transactions: two-phase locking (2PL) simulation TUI/GUI.
A simulation of a lock-based database, where transactions act on shared data.
Each transaction is a sequence of read or write operations on named variables,
and ends with either a commit or abort operation.
A transaction executing a read operation tries to acquire a shared lock on the
variable and, likewise, a transaction executing a write operation tries to
acquire an exclusive lock on the variable. An existing exclusive lock blocks
any attempt to acquire any type of lock. An existing shared lock blocks any
attempt to acquire an exclusive lock. If a transaction can't acquire the lock
it needs, it sleeps until the lock is released. That occurs when the
transaction holding the lock executes a commit or abort.
# Compilation
The core engine that drives the simulation is built as a library (the `lib`
directory) and has no external dependencies. The GUI is done using Qt. Both
use qmake for compilation:
$ qmake
$ make
# Interface
The simulation state is represented on a GUI. Execution plans can be entered
using the text field on the "create transaction" section by either indicating a
file name or writing the operation list directly. The format (in shell syntax)
is a whitespace-separated list of:
${name}.${op}${var}
where `${name}` is the name of the transaction, `${op}` is a single character
representing the operation ('r', 'w', 'c', 'a') and `${var}` is the name of the
variable. An example valid input is:
t1.ra t2.rb t2.wa t1.wb t1.c t2.a
Random lists of operations can be generated on the "random" section.
The "actions" panel can be used to step through the execution, to reset the
execution to the initial state and to clear the list of transactions. During
the execution, the main region displays information about the operations and
transactions and the "locks" region displays the locked variables, locks and
sleeping transactions.
# Testing
The `tests` directory has a script for testing the library without using the
graphical interface. If executed without arguments, it runs a pre-generated
test and makes sure the output is the (also pre-generated) expected. It can
also be passed a file name as argument. In that case, the file contents are
parsed and the simulation is executed while output similar to the "locks"
region of the GUI is written on the standard output:
$ cat tests/t
t1.wa t1.wb t1.wb t1.c
t2.wa t2.wb t2.c
t3.wa t3.wb t3.a
$ ./transactions tests/t
=== locked ===
=== locks ===
=== sleeping ===
t2.wa
=== locked ===
a: -1
=== locks ===
a: t2
=== sleeping ===
t2.wb
=== locked ===
a: -1
b: -1
=== locks ===
a: t2
b: t2
=== sleeping ===
t1.wa
=== locked ===
a: -1
b: -1
=== locks ===
a: t2
b: t2
=== sleeping ===
a: t1
t2.c
=== locked ===
=== locks ===
=== sleeping ===
t1.wa
=== locked ===
a: -1
=== locks ===
a: t1
=== sleeping ===
t3.wa
=== locked ===
a: -1
=== locks ===
a: t1
=== sleeping ===
a: t3
t1.wb
=== locked ===
a: -1
b: -1
=== locks ===
a: t1
b: t1
=== sleeping ===
a: t3
t1.wb
=== locked ===
a: -1
b: -1
=== locks ===
a: t1
b: t1
=== sleeping ===
a: t3
t1.c
=== locked ===
=== locks ===
=== sleeping ===
t3.wa
=== locked ===
a: -1
=== locks ===
a: t3
=== sleeping ===
t3.wb
=== locked ===
a: -1
b: -1
=== locks ===
a: t3
b: t3
=== sleeping ===
t3.a
=== locked ===
=== locks ===
=== sleeping ===
=== locked ===
=== locks ===
=== sleeping ===
TEMPLATE = app
CONFIG *= qt c++11
QT += widgets
INCLUDEPATH = include ../lib/include
DEPENDPATH = $$INCLUDEPATH ../lib
HEADERS = include/*.h
SOURCES = src/*.cpp
LIBS = ../lib/libtransactions.a
OBJECTS_DIR = obj
MOC_DIR = moc
#ifndef CREATE_EDIT_H
#define CREATE_EDIT_H
#include <vector>
#include <QLineEdit>
class Transaction;
class CreateEdit : public QLineEdit {
public:
CreateEdit(QWidget * parent = nullptr) : QLineEdit(parent) {}
std::vector<Transaction *> transactions() const;
private:
std::vector<Transaction *> read_transactions(std::istream * is) const;
};
#endif // CREATE_EDIT_H
#ifndef LOCKED_PANEL_H
#define LOCKED_PANEL_H
#include <map>
#include <string>
#include <QLabel>
class Transaction;
class LockedPanel : public QLabel {
public:
LockedPanel(QWidget * parent = nullptr)
: QLabel(parent) {this->update_text();}
void update_text(
const std::map<std::string, int> & locked =
std::map<std::string, int>());
private:
QString generate_text(const std::map<std::string, int> & locked) const;
};
#endif // LOCKED_PANEL_H
#ifndef LOCKS_PANEL_H
#define LOCKS_PANEL_H
#include <map>
#include <list>
#include <string>
#include <QLabel>
class Transaction;
class LocksPanel : public QLabel {
public:
LocksPanel(QWidget * parent = nullptr)
: QLabel(parent) {this->update_text();}
void update_text(
const std::map<std::string, std::list<Transaction *>> & locks =
std::map<std::string, std::list<Transaction *>>());
private:
QString generate_text(
const std::map<std::string,
std::list<Transaction *>> & locks) const;
};
#endif // LOCKS_PANEL_H
#ifndef MAIN_WINDOW_H
#define MAIN_WINDOW_H
#include <vector>
#include <QMainWindow>
#include "Controller.h"
class QPushButton;
class QCheckBox;
class CreateEdit;
class RandomPanel;
class OperationPanel;
class LockedPanel;
class LocksPanel;
class SleepingPanel;
class Transaction;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget * parent = nullptr);
~MainWindow();
public slots:
void create_transactions();
void random_transactions();
void clear_transactions();
void step();
void reset();
private:
void init();
void update_displays(bool last_sleeping = false);
void highlight_current_operation(bool last_sleeping = false);
QPushButton * m_create_button;
QPushButton * m_random_button;
QPushButton * m_clear_button;
QPushButton * m_step_button;
QPushButton * m_reset_button;
QCheckBox * m_suffle_check;
CreateEdit * m_create_edit;
RandomPanel * m_random_panel;
OperationPanel * m_operation_panel;
LockedPanel * m_locked_panel;
LocksPanel * m_locks_panel;
SleepingPanel * m_sleeping_panel;
Controller m_controller;
std::vector<Transaction *> m_transactions;
};
#endif // MAIN_WINDOW_H
#ifndef OPERATION_PANEL_H
#define OPERATION_PANEL_H
#include <list>
#include <map>
#include <QFrame>
class QVBoxLayout;
class QLabel;
class Operation;
class OperationPanel : public QFrame {
public:
OperationPanel(QWidget * parent = nullptr);
void set_operations(const std::list<Operation *> & operations);
void set_current(const Operation * op, bool current_sleeping = false);
private:
void init();
void create_list(const std::list<Operation *> & operations);
void create_list_line(
std::list<Operation *>::const_iterator * it,
unsigned int n);
void create_lines(const std::list<Operation *> & operations);
QLabel * create_label(const Operation * op);
void clear_operations();
std::map<const Operation *, QLabel *> m_list_labels;
std::map<const Operation *, QLabel *> m_line_labels;
QVBoxLayout * m_list_layout;
QVBoxLayout * m_line_layout;
QLabel * m_list_current;
QLabel * m_line_current;
QPalette m_default_palette;
QPalette m_selected_palette;
QPalette m_done_palette;
QPalette m_sleeping_palette;
};
#endif // OPERATION_PANEL_H
#ifndef RANDOM_PANEL_H
#define RANDOM_PANEL_H
#include <QWidget>
#include <QSpinBox>
class RandomPanel : public QWidget {
public:
RandomPanel(QWidget * parent = nullptr);
unsigned int ntransactions() const
{return this->m_transaction_spin->value();}
unsigned int nvariables() const
{return this->m_variable_spin->value();}
unsigned int min_operations() const
{return this->m_min_operation_spin->value();}
unsigned int max_operations() const
{return this->m_max_operation_spin->value();}
private:
void init();
QSpinBox * m_transaction_spin;
QSpinBox * m_variable_spin;
QSpinBox * m_min_operation_spin;
QSpinBox * m_max_operation_spin;
};
#endif // RANDOM_PANEL_H
#ifndef SLEEPING_PANEL_H
#define SLEEPING_PANEL_H
#include <map>
#include <list>
#include <string>
#include <QLabel>
class Transaction;
class SleepingPanel : public QLabel {
public:
SleepingPanel(QWidget * parent = nullptr)
: QLabel(parent) {this->update_text();}
void update_text(
const std::map<std::string, std::list<Transaction *>> & sleeping =
std::map<std::string, std::list<Transaction *>>());
private:
QString generate_text(
const std::map<std::string, std::list<Transaction *>> & sleeping);
};
#endif // SLEEPING_PANEL_H
#include "CreateEdit.h"
#include <fstream>
#include <sstream>
#include <QFileInfo>
#include "Transaction.h"
#include "TransactionBuilder.h"
std::vector<Transaction *> CreateEdit::transactions() const {
auto text = this->text();
if(text.isEmpty())
return std::vector<Transaction *>();
if(QFileInfo(text).exists()) {
auto is = std::ifstream(text.toStdString());
return this->read_transactions(&is);
} else {
auto is = std::istringstream(text.toStdString());
return this->read_transactions(&is);
}
}
std::vector<Transaction *>
CreateEdit::read_transactions(std::istream * is) const {
std::vector<Transaction *> ret;
if(!*is)
return ret;
*is >> ret;
return ret;
}
#include "LockedPanel.h"
#include <sstream>
#include "Transaction.h"
void LockedPanel::update_text(const std::map<std::string, int> & locked) {
this->setText(this->generate_text(locked));
}
QString LockedPanel::generate_text(
const std::map<std::string, int> & locked) const {
std::stringstream ss;
ss << "Locked:";
for(auto x : locked) {
ss << "\n" << x.first << ": ";
if(x.second == -1)
ss << "w";
else
ss << "r" << x.second;
}
return ss.str().c_str();
}
#include "LocksPanel.h"
#include <sstream>
#include "Transaction.h"
void LocksPanel::update_text(
const std::map<std::string, std::list<Transaction *>> & locks) {
this->setText(this->generate_text(locks));
}
QString LocksPanel::generate_text(
const std::map<std::string, std::list<Transaction *>> & locks) const {
std::stringstream ss;
ss << "Locks:";
for(auto x : locks) {
ss << "\n" << x.first << ":";
for(auto y : x.second)
ss << " " << y->name();
}
return ss.str().c_str();
}
#include "MainWindow.h"
#include <QFrame>
#include <QCheckBox>
#include <QGroupBox>
#include <QDockWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include "Operation.h"
#include "Transaction.h"
#include "TransactionBuilder.h"
#include "CreateEdit.h"
#include "LockedPanel.h"
#include "LocksPanel.h"
#include "OperationPanel.h"
#include "RandomPanel.h"
#include "SleepingPanel.h"
MainWindow::MainWindow(QWidget * parent)
: QMainWindow(parent) {
this->init();
this->m_create_edit->setFocus();
this->m_step_button->setEnabled(false);
this->m_suffle_check->setChecked(true);
connect(
this->m_create_button, SIGNAL(clicked()),
this, SLOT(create_transactions()));
connect(
this->m_random_button, SIGNAL(clicked()),
this, SLOT(random_transactions()));
connect(
this->m_clear_button, SIGNAL(clicked()),
this, SLOT(clear_transactions()));
connect(
this->m_step_button, SIGNAL(clicked()),
this, SLOT(step()));
connect(
this->m_reset_button, SIGNAL(clicked()),
this, SLOT(reset()));
}
MainWindow::~MainWindow() {
for(auto x : this->m_transactions)
delete x;
}
void MainWindow::init() {
this->m_create_button = new QPushButton("Create", this);
this->m_random_button = new QPushButton("Random", this);
this->m_clear_button = new QPushButton("Clear", this);
this->m_step_button = new QPushButton("Step", this);
this->m_reset_button = new QPushButton("Reset", this);
this->m_suffle_check = new QCheckBox("Shuffle", this);
this->m_create_edit = new CreateEdit(this);
this->m_random_panel = new RandomPanel(this);
this->m_operation_panel = new OperationPanel(this);
this->m_locked_panel = new LockedPanel(this);
this->m_locks_panel = new LocksPanel(this);
this->m_sleeping_panel = new SleepingPanel(this);
this->m_create_edit->setMaximumHeight(30);
QFrame * central_frame = new QFrame(this);
QVBoxLayout * central_layout = new QVBoxLayout(central_frame);
central_layout->addWidget(this->m_operation_panel);
central_layout->addStretch(1);
central_frame->setLayout(central_layout);
QGroupBox * bottom_top_group = new QGroupBox("File or text", this);
QHBoxLayout * bottom_top_layout = new QHBoxLayout(bottom_top_group);
bottom_top_layout->addWidget(this->m_create_edit);
bottom_top_layout->addWidget(this->m_create_button);
bottom_top_layout->addWidget(this->m_suffle_check);
bottom_top_group->setLayout(bottom_top_layout);
QGroupBox * bottom_bottom_group = new QGroupBox("Random", this);
QHBoxLayout * bottom_bottom_layout = new QHBoxLayout(bottom_bottom_group);
bottom_bottom_layout->addWidget(this->m_random_panel);
bottom_bottom_layout->addWidget(this->m_random_button);
bottom_bottom_group->setLayout(bottom_bottom_layout);
QFrame * bottom_frame = new QFrame(this);
QVBoxLayout * bottom_layout = new QVBoxLayout(bottom_frame);
bottom_layout->addWidget(bottom_top_group);
bottom_layout->addWidget(bottom_bottom_group);
bottom_frame->setLayout(bottom_layout);
QFrame * right_frame = new QFrame(this);
right_frame->setMinimumSize(200, right_frame->minimumSize().width());
QVBoxLayout * right_layout = new QVBoxLayout(right_frame);
right_layout->addWidget(this->m_locked_panel);
right_layout->addWidget(this->m_locks_panel);
right_layout->addWidget(this->m_sleeping_panel);
right_layout->addStretch(1);
right_frame->setLayout(right_layout);
QFrame * left_frame = new QFrame(this);
QVBoxLayout * left_layout = new QVBoxLayout(left_frame);
left_layout->addWidget(this->m_step_button);
left_layout->addWidget(this->m_reset_button);
left_layout->addWidget(this->m_clear_button);
left_layout->addStretch(2);
left_frame->setLayout(left_layout);
QDockWidget * bottom_dock = new QDockWidget(this);
QDockWidget * right_dock = new QDockWidget(this);
QDockWidget * left_dock = new QDockWidget(this);
bottom_dock->setWindowTitle("Create transactions");
right_dock->setWindowTitle("Locks");
left_dock->setWindowTitle("Actions");
bottom_dock->setWidget(bottom_frame);
right_dock->setWidget(right_frame);
left_dock->setWidget(left_frame);
this->setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
this->setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);