LINUX.ORG.RU

[cl][multithreading][+ sqlite gtk2][something wrong]


0

1

Короче вот база с кладром преобразованная в sqlite3, а вот архив с кодом проекта.

Не самый красивый и правильный код, конечно.

Как воспроизвести непонятки:

1. распаковать архив с базой и проектом

2. >>

(require 'kladr)
(kladr:kladr-open-database #P"path-to-the-database")
(kladr:kladr-make-me-happy)
(kladr:draw-hierarchy-tree)

3. Попытаться быстро открыть элемент дерева пока оно еще не нариосовалось до конца. (Откроется окошко и в нем вживую будет появлятся дерево элементов).

4. теперь вы наблюдаете это

 The function COMMON-LISP:NIL is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Restarts:
 0: [RETURN-FROM-CALLBACK] GTK::RETURN-FROM-CALLBACK
 1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "cl-gtk2 main thread" RUNNING {C159451}>)

Backtrace:
  0: (SB-KERNEL:%COERCE-CALLABLE-TO-FUN NIL)
  1: ((LAMBDA (GTK::DATA)) #.(SB-SYS:INT-SAP #X00000002))
  2: ((LAMBDA (SB-ALIEN::ARGS-POINTER SB-ALIEN::RESULT-POINTER FUNCTION)) -377649361 -377649365 #<FUNCTION (LAMBDA #) {C9817F5}>)
  3: ("foreign function: call_into_lisp")
  4: ("foreign function: #x8053A59")
  5: ("foreign function: funcall3")
  6: ("foreign function: #x1100F55")
  7: ("foreign function: #xB4AD1DA2")
  8: ("foreign function: g_main_context_dispatch")
  9: ("foreign function: #xB4AD5D08")
 10: ("foreign function: g_main_loop_run")
 11: ("foreign function: gtk_main")
 12: ("foreign function: call_into_c")
 13: (GTK:GTK-MAIN)
 14: ((LAMBDA ()))
 15: ((LAMBDA ()))
 16: ((FLET #:WITHOUT-INTERRUPTS-BODY-[BLOCK407]412))
 17: ((FLET SB-THREAD::WITH-MUTEX-THUNK))

draw-hierarchy-tree рисует окошко со деревом кладра. Добавление элементов в дерево производится в паралельном потоке (bordeaux-threads), плюс когда раскрываешь элемент дерева, то в паралельном потоке заполняются его дочерние элементы (типа подгрузка элементов по требованию).

Методы вставки элементов в дерево выполняются в макросах gtk:within-main-loop-and-wait. Распарсить вывод слайма не получилось. Чето совсем не понятно из-за чего такое.

Ответ на: комментарий от ananas

нуууу .. проект какбы на коммно лиспе. там в биндинге нет метода gtk_widget_freeze_child_notify. Там другой подход: все что из других потоков вызывается (не в потоке gtk) должно быть в макросе within-main-loop

Ждем виноградова с архимагом.

s9gf4ult ★★ ()

s9gf4ult> (kladr:kladr-make-me-happy)

это лучше заменить на

(kladr:kladr-with-transaction
  (kladr:kladr-make-me-happy))

чтобы не тормозило, а то там много инсертов.

s9gf4ult ★★ ()
Ответ на: комментарий от ananas

Дак тогда загрузка элементов не будет интерактивной и нет никакого профита в том что это делается в отдельном потоке, не ?

s9gf4ult ★★ ()
Ответ на: комментарий от ananas

ладно щас попробую так, но хотелось бы чтобы было так динамичненько в стиле гугля. Открываешь окошко - а там элементы грузятся и ты такой прямо в процессе загрузки раскрываешь элементы и смотришь что там есть, а то что там есть тоже динамически загружается на твоих глазах.

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

> И надо узнать же корень проблемы чтобы понять что не так.

корень проблемы в том, что где-то не нормально устанавливается обработчик row-expanded. но где и почему - моих знаний cl тут явно не достаточно

ananas ★★★★★ ()
Ответ на: комментарий от s9gf4ult

> Это в потоке gtk делать надо ?

да. это прогон нескольких итераций главного цикла для обработки всей очереди текущих сообщений. чтобы пользователь не сделал каку-нибудь херню до завершения критичных действий.

ananas ★★★★★ ()
Ответ на: комментарий от ananas

Интересно, дома на одноядерной машине вызвать подобное не могу.

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

> Ждем виноградова с архимагом.

Меня ждать не стоит - в GTK не силён ). Судя по стэку вызовов надо внимательно посмотреть последний вызов, но так не понять что там, поэтому читай http://archimag.lisper.ru/permalink/posts/a830cc75006484db250f40f1eeb505571fd... )) Как найдёшь проблемно место - выставь (declare (debug 3)) - всё проще будет.

archimag ★★★ ()
Ответ на: комментарий от archimag

1. Архимаг, ты не прав. Если уж на то пошло, то читать по твоей ссылке следует не ТС, а авторам sbcl (ибо лямбды в бэктрейсе там ихние, из потрохов sb-alient). Но даже этого не нужно, т. к. sbcl умный и нажатие v на соответствующей строке бэктрейса показывает нам тот код, откуда эта самая лямбда взялась (defcallback call-from-main-loop-callback из gtk.misc.lisp). В свете этого, твой совет пихать именованные лямбды всюду куда только можно выглядит... ну, не совсем правильным.

2. Теперь по сути проблемы. Насколько я понял, причина в том, что within-main-loop не потокобезопасен - по крайней мере в том виде, как оно сейчас там сделано, оно гарантированно корректно работает только в пределах одного треда. Очевидный источник проблемы - то, каким образом передаётся указание на код, который должен быть исполнен в main loop, вовнутрь gtk: лямбда с этим кодом кладётся в массив и индекс этого массива передаётся как параметр. При вызове из main loop, соответственно, по индексу вытаскивается лямбда из массива и вызывается, после чего из массива убирается. Всё это делается без каких-либо блокировок, поэтому при попытке организовать таким образом взаимодействие между тредами мы быстро получаем data race. Такая хитрая схема нужна чтобы избежать проблем при перемещении объекта сборщиком мусора (про который гтк, естественно, не в курсе).

Очевидный (но не факт что полностью завершённый) способ решить проблему - защитить работу с вышеупомянутым массивом (функции allocate-stable-pointer и пр.) мутексом. Не факт что завершённый - потому что я не уверен в тредобезопасности остального кода (как из gtk, так и из cl-gtk), который тут задействован. Хотя, если верить тому, что написано в документации gtk ("...but sources can be added to it and removed from it from other threads"), всё должно нормально работать.

slav ★★ ()
Ответ на: комментарий от slav

> Но даже этого не нужно, т. к. sbcl умный и нажатие v на

соответствующей строке бэктрейса показывает нам тот код


Вообще говоря нет.

твой совет пихать именованные лямбды всюду куда только можно

выглядит...



Хм, так делает, например, Nikodemus Siivola.

archimag ★★★ ()
Ответ на: комментарий от archimag

выставь (declare (debug 3))

У меня оно поставлено в /etc/sbclrc все равно что - то не просто. Как я говорил ранее на одноядерном сейчас воспроизвести не могу.

s9gf4ult ★★ ()
Ответ на: комментарий от ananas

Кароче таки дела...

Переписал я это дело на Си. Теперь эта штука выклядит так:

/* @(#)kladr.c
 */

#include <gtk/gtk.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct {
  sqlite3 *connection;
  GtkTreeModel *model;
  GtkTreeView *view;
} ModelAndConnection;

typedef struct {
  ModelAndConnection *model_and_connection;
  GtkTreeIter *parent;
} TreeParent;

gboolean on_window_close(GtkWidget *window, GdkEvent *event, gpointer user_data)
{
  ModelAndConnection *data = (ModelAndConnection *)user_data;
  sqlite3_close(data->connection);
  free(data);
  gtk_main_quit();
  return FALSE;
};

GtkTreeIter *append_value(GtkTreeStore *store, GtkTreeIter *parent, int val0, gchar *val1, gchar *val2)
{
  GtkTreeIter set;
  gtk_tree_store_append(store, &set, parent);
  gtk_tree_store_set(store, &set, 0, val0, 1, val1, 2, val2, -1);
  return gtk_tree_iter_copy(&set);
}

gpointer child_builder_thread(gpointer user_data)
{
  TreeParent *data = (TreeParent *)user_data;
  gdk_threads_enter();
  gtk_widget_freeze_child_notify(GTK_WIDGET(data->model_and_connection->view));
  
  GValue rvalue;
  gtk_tree_model_get_value(GTK_TREE_MODEL(data->model_and_connection->model),
                           data->parent,
                           0, &rvalue);

  char *childs = "select k.id, t.name, k.name from kladr_objects k inner join short_names t on k.short_id = t.id inner join kladr_hierarchy h on h.child = k.id where h.parent = ? order by t.name, k.name";
  sqlite3_stmt * chstmt;
  if (SQLITE_OK != sqlite3_prepare(data->model_and_connection->connection, childs, -1, &chstmt, NULL)) {
    g_printerr("Can not create statement %s", childs);
    return NULL;
  }

  sqlite3_bind_int(chstmt, 1, g_value_get_int(&rvalue));
  while (SQLITE_ROW == sqlite3_step(chstmt)) {
    GtkTreeIter *child = append_value(GTK_TREE_STORE(data->model_and_connection->model),
                                      data->parent,
                                      sqlite3_column_int(chstmt, 0),
                                      g_strdup(sqlite3_column_text(chstmt, 1)),
                                      g_strdup(sqlite3_column_text(chstmt, 2)));
  }
  sqlite3_finalize(chstmt);
    
  gtk_widget_thaw_child_notify(GTK_WIDGET(data->model_and_connection->view));
  gdk_threads_leave();
  g_free(user_data);
  return NULL;
}
  

void build_children(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
{
  TreeParent *data = g_malloc(sizeof(TreeParent));
  data->parent = iter;
  data->model_and_connection = (ModelAndConnection *)user_data;
  if (NULL == g_thread_create(&child_builder_thread, data, FALSE, NULL)) {
    g_printerr("Can not create thread");
  }
  return;
};



gpointer root_builder_thread(gpointer user_data)
{
  ModelAndConnection *data = (ModelAndConnection *)user_data;
  char *select_roots = "select k.id, t.name, k.name from kladr_objects k inner join short_names t on k.short_id = t.id where exists(select h.* from kladr_hierarchy h where h.parent = k.id) and not exists(select hh.* from kladr_hierarchy hh where hh.child = k.id) order by t.name, k.name";
  char *select_childs = "select k.id, t.name, k.name from kladr_objects k inner join short_names t on k.short_id = t.id inner join kladr_hierarchy h on h.child = k.id where h.parent = ? order by t.name, k.name";
  sqlite3_stmt *root_stmt, *child_stmt;
  if (SQLITE_OK != sqlite3_prepare(data->connection, select_roots, -1, &root_stmt, NULL)) {
    g_printerr ("can not prepare statement %s", select_roots);
    return NULL;
  }
  if (SQLITE_OK != sqlite3_prepare(data->connection, select_childs, -1, &child_stmt, NULL)) {
    g_printerr("can not prepare %s", select_childs);
    sqlite3_finalize(root_stmt);
    return NULL;
  }
  while (SQLITE_ROW == sqlite3_step(root_stmt)) {
    gdk_threads_enter();
    int root_id = sqlite3_column_int(root_stmt, 0);
    //gtk_widget_freeze_child_notify(GTK_WIDGET(data->view));
    GtkTreeIter *parent =  append_value(GTK_TREE_STORE(data->model), NULL,
                                        root_id,
                                        g_strdup(sqlite3_column_text(root_stmt, 1)),
                                        g_strdup(sqlite3_column_text(root_stmt, 2)));
    //gtk_widget_thaw_child_notify(GTK_WIDGET(data->view));
    gdk_threads_leave();
    
    sqlite3_bind_int(child_stmt, 1, root_id);
    while(SQLITE_ROW == sqlite3_step(child_stmt)) {
      gdk_threads_enter();
      //gtk_widget_freeze_child_notify(GTK_WIDGET(data->view));
      GtkTreeIter *child = append_value(GTK_TREE_STORE(data->model), parent,
                                        sqlite3_column_int(child_stmt, 0),
                                        g_strdup(sqlite3_column_text(child_stmt, 1)),
                                        g_strdup(sqlite3_column_text(child_stmt, 2)));
      //gtk_widget_thaw_child_notify(GTK_WIDGET(data->view));
      gtk_tree_iter_free(child);
      gdk_flush();
      gdk_threads_leave();
    };
    sqlite3_reset(child_stmt);
    sqlite3_clear_bindings(child_stmt);
    
    gdk_threads_enter();
    gtk_tree_iter_free(parent);
    gdk_threads_leave();
   }
  sqlite3_finalize(root_stmt);
  sqlite3_finalize(child_stmt);
  return NULL;
};

void run_building_root(gpointer user_data)
{
  if (NULL == g_thread_create(&root_builder_thread, user_data, FALSE, NULL)) {
    g_printerr("Can not create thread");
  }
};


void build_and_run(char *filename)
{
  sqlite3 *connection;
  if (sqlite3_open(filename, /*@out@*/ &connection) != SQLITE_OK) {
    printf("Can not open file %s", filename);
    return;
  }
  g_print("Connection set");
  GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
  GtkWidget *view = gtk_tree_view_new();
  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(sw));
  gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(view));
  GtkTreeModel *model = GTK_TREE_MODEL(gtk_tree_store_new(3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING));
  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
  GtkTreeViewColumn *col1 = gtk_tree_view_column_new();
  GtkTreeViewColumn *col2 = gtk_tree_view_column_new();
  GtkCellRenderer *ren1 = gtk_cell_renderer_text_new();
  GtkCellRenderer *ren2 = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(col1, ren1, TRUE);
  gtk_tree_view_column_add_attribute(col1, ren1, "text", 1);
  gtk_tree_view_column_pack_start(col2, ren2, TRUE);
  gtk_tree_view_column_add_attribute(col2, ren2, "text", 2);
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col1);
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col2);

  ModelAndConnection *data = malloc(sizeof(ModelAndConnection));
  if (NULL == data) {
    printf("Can not allocate memory");
  }
  data->connection = connection;
  data->model = model;
  data->view = GTK_TREE_VIEW(view);

  g_signal_connect(G_OBJECT(view), "row-expanded", G_CALLBACK(build_children), data);
  g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(on_window_close), data);
  gtk_widget_show_all(GTK_WIDGET(window));

  run_building_root(data);

};

int main(int argc, char **argv) {
  if (argc == 2) {
    g_thread_init(NULL);
    gdk_threads_init();
    gtk_init(&argc, &argv);

    build_and_run(argv[1]);
    gdk_threads_enter();
    gtk_main();
    gdk_threads_leave();
  }
  return 0;
}
Делал в соответствии с примером с документашки. Судя по ней, синхронизацию между потокам организуют волшебные вызовы gdk_threads_enter() и gdk_threads_leave() между которыми код, якобы исполняется всегда в потоке ГТК. У меня сегфолтится, когда нажимаешь на стрелочку, пока грузится дерево.

Что делать ? И как вообще пишутся на си подобные приложения типа наутилуса того-же. Там ведь информация о файлах в каталоге на лету загрузается и обновляется в процессе просмотра каталога (пользователь не ждет пока обновится информация о всех файлах, уставившись в пустое окно).

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

похоже, проблема в том, что ты вешаешься на row-expanded. этот сигнал срабатывает после того, как ветка развернута. для заполнения данными надо цеплять test-expand-row.

з.ы. ты бы базу для приличия в нормальном формате выложил бы

ananas ★★★★★ ()
Ответ на: комментарий от s9gf4ult

В отладчике кстати очень трудно вызвать сегфолт. А без него - пожалуйста.

s9gf4ult ★★ ()
Ответ на: комментарий от ananas
#0  0x00007ffff5a6f1d9 in type_check_is_value_type_U (type=7050704) at gtype.c:4092
#1  0x00007ffff5a6f328 in g_type_check_value_holds (value=0x7fffeb251c30, type=24) at gtype.c:4133
#2  0x00007ffff5a75f51 in g_value_get_int (value=0x7fffeb251c30) at gvaluetypes.c:755
#3  0x0000000000401c0a in child_builder_thread (user_data=0x34b8780) at kladr.c:57
#4  0x00007ffff4ec7c0a in g_thread_create_proxy (data=0x3610960) at gthread.c:1955
#5  0x00007ffff4c348e4 in start_thread () from /lib/libpthread.so.0
#6  0x00007ffff49a627d in clone () from /lib/libc.so.6

вот стек трейс

sqlite3_bind_int(chstmt, 1, g_value_get_int(&rvalue));
вот строка из сорца (kladr.c:57). О чем это может говоирть ?

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

> О чем это может говоирть ?

что в rvalue непойми что. поэтому нефиг баловаться с GValue и проще получать int напрямую через gtk_tree_model_get()

ananas ★★★★★ ()
Ответ на: комментарий от ananas

проблема решилась.

Если интересно, то дело было в итераторе, который я передавал в создаваевый тред, очевидимо, что GTK освобождает итератор, который передает обработчику и его надо скопировать перед передачей в другой тред.

s9gf4ult ★★ ()
Ответ на: комментарий от baverman

Это почему это ? Пользюк же не будет ждать минуту пока загрузятся все объекты из базы (а это примерно столько и длится), смотря на белый экран который даже закрыть нельзя.

s9gf4ult ★★ ()
Ответ на: комментарий от baverman

Кстати может если ты знаешь.. как приостановить тред GThread ?

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

Пользюк же не будет ждать минуту пока загрузятся все объекты из базы

Если запрос долгий, то да, его лучше асинхронно (или в другом потоке) дергать. А добавлять элементы в модель можно и в основном потоке, периодически вызывая gtk_events_pending/gtk_main_iteration. Меньше геморроя с синхронизацией.

как приостановить тред GThread

Тредами, при разработке гуя на GTK, пришлось воспользоваться только один раз, да и то обошелся питонячьими, поэтому ничего подсказать не могу.

baverman ★★★ ()
Ответ на: комментарий от baverman

baverman> А добавлять элементы в модель можно и в основном потоке, периодически вызывая gtk_events_pending/gtk_main_iteration

Это как ?

s9gf4ult ★★ ()
Ответ на: комментарий от baverman

Тобиш в одном потоке дергать объекты из базы и кидать их в модель, при этом оббивать весь метод вставки конструкциями

gtk_widget_freeze_child_notify()
................
gtk_widget_thaw_child_notify()


А в потоке GTK раз в несколько времени делать
while (gtk_events_pending())
  gtk_main_iteration()

чтобы обработать события, которые пришли от модели так? Тогда как сделать вызов раз в секунду например ? Очевидимо как-то генерировать этот сигнал или повешать на обработку какого - то таймера ?

s9gf4ult ★★ ()
Ответ на: комментарий от s9gf4ult

Тогда как сделать вызов раз в секунду например

По системному времени. Я, например, сделал итератор, который при получении очередного элемента, сравнивает текущее время с последним и если оно больше нужного интервала, обрабатывает накопившиеся события.

Очевидимо как-то генерировать этот сигнал или повешать на обработку какого - то таймера?

Это не сработает. Так как в данный момент времени выполняется твой код, а модель обработки/эммита сигналов в gtk асинхронная.

kladr

Вообще, если быть нудным и памятуя, что такое кладр...

1) Я бы сделал отложенную загрузку — вместо реальных детей добавлять dummy значения. То есть первоначальный вид тристора будет такой:

+--item1
   +--dummy
+--item2
   +--dummy

А обработчике row-expanded догружать необходимые элементы, опять таки с dummy.

2) Сделать кастомную модель, на основе загруженных записей. Тогда вообще не надо думать ни об отзывчивости ни о тредах (пока данных сравнительно мало, конечно).

Смысл: по-возможности сократить поток данных в TreeStore.

baverman ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.