e config - move config saving to threads to avoid stutters/io blocks

so our writes sometimes would get stuck because kernel io buffers are
full and writes are slow. on specific machines with super slow write
media and small amounts of ram this was bad.

this moves writing totally to threads. the eet file is opened in a
thread and closed in the same thread. only the eet_write/eet_data_write are
done in the mainloop. this is a 'walk struct, serialise it and compress
it" which compared to blocking for possibly multiple seconds in a
write/close/rename backup cfg files doing real io to kernel 9even
though kernle should buffer these)... is a hell of a lot better.

so sure. we block lock enough to walk the structures/lists, encode the
blob and put it through a fast lz4 compress cycle and drop into memory.
the actual write happens in the thread when the file is closed and
that is a vast improvement if you hit these cases.
This commit is contained in:
Carsten Haitzler 2020-09-04 13:58:14 +01:00
parent 423e26cb8e
commit da1f9bc5f6
3 changed files with 264 additions and 242 deletions

View File

@ -205,6 +205,7 @@ extern E_API Eina_Bool e_precache_end;
extern E_API Eina_Bool x_fatal;
extern E_API Eina_Bool after_restart;
extern E_API Eina_Bool e_main_loop_running;
extern EINTERN const char *e_first_frame;
extern EINTERN double e_first_frame_start_time;

View File

@ -9,7 +9,6 @@ static int _e_config_revisions = 9;
static void _e_config_save_cb(void *data);
static void _e_config_free(E_Config *cfg);
static Eina_Bool _e_config_cb_timer(void *data);
static int _e_config_eet_close_handle(Eet_File *ef, char *file);
/* local subsystem globals */
static int _e_config_save_block = 0;
@ -49,7 +48,10 @@ E_API int E_EVENT_CONFIG_ICON_THEME = 0;
E_API int E_EVENT_CONFIG_MODE_CHANGED = 0;
E_API int E_EVENT_CONFIG_LOADED = 0;
static E_Dialog *_e_config_error_dialog = NULL;
static Eina_Lock _e_config_pending_files_lock;
static Eina_Hash *_e_config_pending_files = NULL;
static Eina_Thread_Queue *_e_config_thread_thq = NULL;
static Eina_List *handlers = NULL;
typedef struct _E_Color_Class
@ -60,6 +62,19 @@ typedef struct _E_Color_Class
int r3, g3, b3, a3;
} E_Color_Class;
typedef enum
{
E_CONFIG_SAVE_THREAD_SAVE,
E_CONFIG_SAVE_THREAD_QUIT
} E_Config_Save_Thread_Message_Type;
typedef struct _E_Config_Save_Thread_Message
{
Eina_Thread_Queue_Msg head;
E_Config_Save_Thread_Message_Type type;
char *path, *destpath;
} E_Config_Save_Thread_Message;
static Eina_Bool
_e_config_cb_efreet_cache_update(void *data EINA_UNUSED, int type EINA_UNUSED, void *ev EINA_UNUSED)
{
@ -78,13 +93,6 @@ _e_config_cb_efreet_cache_update(void *data EINA_UNUSED, int type EINA_UNUSED, v
}
static void
_e_config_error_dialog_cb_delete(void *dia)
{
if (dia == _e_config_error_dialog)
_e_config_error_dialog = NULL;
}
static const char *
_e_config_profile_name_get(Eet_File *ef)
{
@ -117,6 +125,223 @@ _e_config_profile_name_get(Eet_File *ef)
return s;
}
static Eet_File *
_e_config_pending_file_find(const char *path)
{
Eet_File *ef, *ef2;
eina_lock_take(&_e_config_pending_files_lock);
ef = eina_hash_find(_e_config_pending_files, path);
if (!ef)
{
eina_lock_release(&_e_config_pending_files_lock);
// do this unlocked as it could block for a while
ef = eet_open(path, EET_FILE_MODE_WRITE);
eina_lock_take(&_e_config_pending_files_lock);
if (ef)
{
// handle race due to the above unlock and lock - should be
// super rare...
ef2 = eina_hash_find(_e_config_pending_files, path);
if (ef2)
{
eina_hash_del(_e_config_pending_files, path, ef2);
eet_close(ef2);
}
eina_hash_add(_e_config_pending_files, path, ef);
}
else
printf("CF: Error: file find %s - create new fail\n", path);
}
eina_lock_release(&_e_config_pending_files_lock);
return ef;
}
static Eina_Bool
_e_config_pending_file_del(const char *path)
{
Eet_File *ef;
Eina_Bool ok = EINA_FALSE;
eina_lock_take(&_e_config_pending_files_lock);
ef = eina_hash_find(_e_config_pending_files, path);
if (ef)
{
Eet_Error err;
const char *erstr = "";
eina_hash_del(_e_config_pending_files, path, ef);
eina_lock_release(&_e_config_pending_files_lock);
err = eet_close(ef);
switch (err)
{
case EET_ERROR_NONE:
ok = EINA_TRUE;
/* all good - no error */
break;
case EET_ERROR_BAD_OBJECT:
erstr = _("The EET file handle is bad.");
break;
case EET_ERROR_EMPTY:
erstr = _("The file data is empty.");
break;
case EET_ERROR_NOT_WRITABLE:
erstr = _("The file is not writable. Perhaps the disk is read-only<ps/>or you lost permissions to your files.");
break;
case EET_ERROR_OUT_OF_MEMORY:
erstr = _("Memory ran out while preparing the write.<ps/>Please free up memory.");
break;
case EET_ERROR_WRITE_ERROR:
erstr = _("This is a generic error.");
break;
case EET_ERROR_WRITE_ERROR_FILE_TOO_BIG:
erstr = _("The settings file is too large.<ps/>It should be very small (a few hundred KB at most).");
break;
case EET_ERROR_WRITE_ERROR_IO_ERROR:
erstr = _("You have I/O errors on the disk.<ps/>Maybe it needs replacing?");
break;
case EET_ERROR_WRITE_ERROR_OUT_OF_SPACE:
erstr = _("You ran out of space while writing the file.");
break;
case EET_ERROR_WRITE_ERROR_FILE_CLOSED:
erstr = _("The file was closed while writing.");
break;
case EET_ERROR_MMAP_FAILED:
erstr = _("Memory-mapping (mmap) of the file failed.");
break;
case EET_ERROR_X509_ENCODING_FAILED:
erstr = _("X509 Encoding failed.");
break;
case EET_ERROR_SIGNATURE_FAILED:
erstr = _("Signature failed.");
break;
case EET_ERROR_INVALID_SIGNATURE:
erstr = _("The signature was invalid.");
break;
case EET_ERROR_NOT_SIGNED:
erstr = _("Not signed.");
break;
case EET_ERROR_NOT_IMPLEMENTED:
erstr = _("Feature not implemented.");
break;
case EET_ERROR_PRNG_NOT_SEEDED:
erstr = _("PRNG was not seeded.");
break;
case EET_ERROR_ENCRYPT_FAILED:
erstr = _("Encryption failed.");
break;
case EET_ERROR_DECRYPT_FAILED:
erstr = _("Decryption failed.");
break;
default: /* if we get here eet added errors we don't know */
erstr = _("The error is unknown to Enlightenment.");
break;
}
if (!ok) printf("CF: Write Error: %s\n", erstr);
}
else
eina_lock_release(&_e_config_pending_files_lock);
return ok;
}
static void
_e_config_save_thread_main(void *data EINA_UNUSED, Ecore_Thread *eth)
{
E_Config_Save_Thread_Message *msg;
void *ref;
Eina_Bool run = EINA_TRUE;
while (run)
{
msg = eina_thread_queue_wait(_e_config_thread_thq, &ref);
switch (msg->type)
{
case E_CONFIG_SAVE_THREAD_SAVE:
if (_e_config_pending_file_del(msg->path))
{
Eina_Bool ret = EINA_TRUE;
if (_e_config_revisions > 0)
{
int i;
char bsrc[4096], bdst[4096];
for (i = _e_config_revisions; i > 1; i--)
{
snprintf(bsrc, sizeof(bsrc), "%s.%i", msg->destpath, i - 1);
snprintf(bdst, sizeof(bdst), "%s.%i", msg->destpath, i);
if ((ecore_file_exists(bsrc)) &&
(ecore_file_size(bsrc)))
{
ret = ecore_file_mv(bsrc, bdst);
if (!ret)
{
printf("CF: Error: Can't rename %s to %s\n", bsrc, bdst);
break;
}
}
}
if (ret)
{
snprintf(bdst, sizeof(bdst), "%s.1", msg->destpath);
ecore_file_mv(msg->destpath, bdst);
}
}
if (!ecore_file_mv(msg->path, msg->destpath))
printf("CF: Error: Can't rename %s to %s\n", msg->path, msg->destpath);
// open another tmp file now in a thread ready for writes next
// time. This can just dangle - no harm
_e_config_pending_file_find(msg->path);
ecore_thread_feedback(eth, strdup(msg->destpath));
}
free(msg->path);
free(msg->destpath);
break;
case E_CONFIG_SAVE_THREAD_QUIT:
run = EINA_FALSE;
break;
default:
break;
}
eina_thread_queue_wait_done(_e_config_thread_thq, ref);
}
}
static void
_e_config_save_thread_notify(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED, void *msgdata)
{
char *path = msgdata;
free(path);
}
static void
_e_config_save_thread_end(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED)
{
ecore_main_loop_quit();
}
static void
_e_config_save_thread_cancel(void *data EINA_UNUSED, Ecore_Thread *eth EINA_UNUSED)
{
ecore_main_loop_quit();
}
static void
_e_config_save_thread_send(E_Config_Save_Thread_Message_Type type, const char *path, const char *destpath)
{
E_Config_Save_Thread_Message *msg;
void *ref;
msg = eina_thread_queue_send
(_e_config_thread_thq, sizeof(E_Config_Save_Thread_Message), &ref);
msg->type = type;
if (path) msg->path = strdup(path);
else msg->path = NULL;
if (destpath) msg->destpath = strdup(destpath);
else msg->destpath = NULL;
eina_thread_queue_send_done(_e_config_thread_thq, ref);
}
static void
_e_config_edd_shutdown(void)
{
@ -778,6 +1003,16 @@ e_config_init(void)
E_EVENT_CONFIG_MODE_CHANGED = ecore_event_type_new();
E_EVENT_CONFIG_LOADED = ecore_event_type_new();
eina_lock_new(&_e_config_pending_files_lock);
_e_config_pending_files = eina_hash_string_superfast_new(NULL);
_e_config_thread_thq = eina_thread_queue_new();
ecore_thread_feedback_run(_e_config_save_thread_main,
_e_config_save_thread_notify,
_e_config_save_thread_end,
_e_config_save_thread_cancel,
NULL, EINA_TRUE);
/* if environment var set - use this profile name */
_e_config_profile = eina_stringshare_add(getenv("E_CONF_PROFILE"));
@ -1752,6 +1987,12 @@ e_config_save_flush(void)
_e_config_save_defer = NULL;
_e_config_save_cb(NULL);
}
if (!e_main_loop_running)
{
_e_config_save_thread_send(E_CONFIG_SAVE_THREAD_QUIT, NULL, NULL);
// wait for save thread to exit...
ecore_main_loop_begin();
}
}
E_API void
@ -1962,39 +2203,6 @@ e_config_domain_system_load(const char *domain, E_Config_DD *edd)
return data;
}
static void
_e_config_mv_error(const char *from, const char *to)
{
E_Dialog *dia;
char buf[8192];
if (_e_config_error_dialog) return;
dia = e_dialog_new(NULL, "E", "_sys_error_logout_slow");
EINA_SAFETY_ON_NULL_RETURN(dia);
e_dialog_title_set(dia, _("Enlightenment Settings Write Problems"));
e_dialog_icon_set(dia, "dialog-error", 64);
snprintf(buf, sizeof(buf),
_("Enlightenment has had an error while moving config files<ps/>"
"from:<ps/>"
"%s<ps/>"
"<ps/>"
"to:<ps/>"
"%s<ps/>"
"<ps/>"
"The rest of the write has been aborted for safety.<ps/>"),
from, to);
e_dialog_text_set(dia, buf);
e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
e_dialog_button_focus_num(dia, 0);
elm_win_center(dia->win, 1, 1);
e_object_del_attach_func_set(E_OBJECT(dia),
_e_config_error_dialog_cb_delete);
e_dialog_show(dia);
_e_config_error_dialog = dia;
}
E_API int
e_config_profile_save(void)
{
@ -2015,48 +2223,12 @@ e_config_profile_save(void)
e_user_dir_concat_static(buf, "config/profile.cfg");
e_user_dir_concat_static(buf2, "config/profile.cfg.tmp");
ef = eet_open(buf2, EET_FILE_MODE_WRITE);
ef = _e_config_pending_file_find(buf2);
if (ef)
{
ok = eet_write(ef, "config", _e_config_profile,
strlen(_e_config_profile), 0);
if (_e_config_eet_close_handle(ef, buf2))
{
Eina_Bool ret = EINA_TRUE;
if (_e_config_revisions > 0)
{
int i;
char bsrc[4096], bdst[4096];
for (i = _e_config_revisions; i > 1; i--)
{
e_user_dir_snprintf(bsrc, sizeof(bsrc), "config/profile.%i.cfg", i - 1);
e_user_dir_snprintf(bdst, sizeof(bdst), "config/profile.%i.cfg", i);
if ((ecore_file_exists(bsrc)) &&
(ecore_file_size(bsrc)))
{
ret = ecore_file_mv(bsrc, bdst);
if (!ret)
{
_e_config_mv_error(bsrc, bdst);
break;
}
}
}
if (ret)
{
e_user_dir_snprintf(bsrc, sizeof(bsrc), "config/profile.cfg");
e_user_dir_snprintf(bdst, sizeof(bdst), "config/profile.1.cfg");
ecore_file_mv(bsrc, bdst);
// if (!ret)
// _e_config_mv_error(bsrc, bdst);
}
}
ret = ecore_file_mv(buf2, buf);
if (!ret) _e_config_mv_error(buf2, buf);
}
ecore_file_unlink(buf2);
_e_config_save_thread_send(E_CONFIG_SAVE_THREAD_SAVE, buf2, buf);
}
return ok;
}
@ -2076,7 +2248,7 @@ e_config_domain_save(const char *domain, E_Config_DD *edd, const void *data)
{
Eet_File *ef;
char buf[4096], buf2[4096];
int ok = 0, ret;
int ok = 0;
size_t len, len2;
if (_e_config_save_block) return 0;
@ -2101,36 +2273,12 @@ e_config_domain_save(const char *domain, E_Config_DD *edd, const void *data)
memcpy(buf2, buf, len);
memcpy(buf2 + len, ".tmp", sizeof(".tmp"));
ef = eet_open(buf2, EET_FILE_MODE_WRITE);
ef = _e_config_pending_file_find(buf2);
if (ef)
{
ok = eet_data_write(ef, edd, "config", data, 1);
if (_e_config_eet_close_handle(ef, buf2))
{
if (_e_config_revisions > 0)
{
int i;
char bsrc[4096], bdst[4096];
for (i = _e_config_revisions; i > 1; i--)
{
e_user_dir_snprintf(bsrc, sizeof(bsrc), "config/%s/%s.%i.cfg", _e_config_profile, domain, i - 1);
e_user_dir_snprintf(bdst, sizeof(bdst), "config/%s/%s.%i.cfg", _e_config_profile, domain, i);
if ((ecore_file_exists(bsrc)) &&
(ecore_file_size(bsrc)))
{
ecore_file_mv(bsrc, bdst);
}
}
e_user_dir_snprintf(bsrc, sizeof(bsrc), "config/%s/%s.cfg", _e_config_profile, domain);
e_user_dir_snprintf(bdst, sizeof(bdst), "config/%s/%s.1.cfg", _e_config_profile, domain);
ecore_file_mv(bsrc, bdst);
}
ret = ecore_file_mv(buf2, buf);
if (!ret)
ERR("*** Error saving config. ***");
}
ecore_file_unlink(buf2);
ok = eet_data_write(ef, edd, "config", data,
EET_COMPRESSION_SUPERFAST);
_e_config_save_thread_send(E_CONFIG_SAVE_THREAD_SAVE, buf2, buf);
}
return ok;
}
@ -2524,135 +2672,3 @@ _e_config_cb_timer(void *data)
e_util_dialog_show(_("Settings Upgraded"), "%s", (char *)data);
return 0;
}
static int
_e_config_eet_close_handle(Eet_File *ef, char *file)
{
Eet_Error err;
char *erstr = NULL;
err = eet_close(ef);
switch (err)
{
case EET_ERROR_NONE:
/* all good - no error */
break;
case EET_ERROR_BAD_OBJECT:
erstr = _("The EET file handle is bad.");
break;
case EET_ERROR_EMPTY:
erstr = _("The file data is empty.");
break;
case EET_ERROR_NOT_WRITABLE:
erstr = _("The file is not writable. Perhaps the disk is read-only<ps/>or you lost permissions to your files.");
break;
case EET_ERROR_OUT_OF_MEMORY:
erstr = _("Memory ran out while preparing the write.<ps/>Please free up memory.");
break;
case EET_ERROR_WRITE_ERROR:
erstr = _("This is a generic error.");
break;
case EET_ERROR_WRITE_ERROR_FILE_TOO_BIG:
erstr = _("The settings file is too large.<ps/>It should be very small (a few hundred KB at most).");
break;
case EET_ERROR_WRITE_ERROR_IO_ERROR:
erstr = _("You have I/O errors on the disk.<ps/>Maybe it needs replacing?");
break;
case EET_ERROR_WRITE_ERROR_OUT_OF_SPACE:
erstr = _("You ran out of space while writing the file.");
break;
case EET_ERROR_WRITE_ERROR_FILE_CLOSED:
erstr = _("The file was closed while writing.");
break;
case EET_ERROR_MMAP_FAILED:
erstr = _("Memory-mapping (mmap) of the file failed.");
break;
case EET_ERROR_X509_ENCODING_FAILED:
erstr = _("X509 Encoding failed.");
break;
case EET_ERROR_SIGNATURE_FAILED:
erstr = _("Signature failed.");
break;
case EET_ERROR_INVALID_SIGNATURE:
erstr = _("The signature was invalid.");
break;
case EET_ERROR_NOT_SIGNED:
erstr = _("Not signed.");
break;
case EET_ERROR_NOT_IMPLEMENTED:
erstr = _("Feature not implemented.");
break;
case EET_ERROR_PRNG_NOT_SEEDED:
erstr = _("PRNG was not seeded.");
break;
case EET_ERROR_ENCRYPT_FAILED:
erstr = _("Encryption failed.");
break;
case EET_ERROR_DECRYPT_FAILED:
erstr = _("Decryption failed.");
break;
default: /* if we get here eet added errors we don't know */
erstr = _("The error is unknown to Enlightenment.");
break;
}
if (erstr)
{
/* delete any partially-written file */
ecore_file_unlink(file);
/* only show dialog for first error - further ones are likely */
/* more of the same error */
if (!_e_config_error_dialog)
{
E_Dialog *dia;
dia = e_dialog_new(NULL, "E", "_sys_error_logout_slow");
if (dia)
{
char buf[8192];
e_dialog_title_set(dia, _("Enlightenment Settings Write Problems"));
e_dialog_icon_set(dia, "dialog-error", 64);
snprintf(buf, sizeof(buf),
_("Enlightenment has had an error while writing<ps/>"
"its config file.<ps/>"
"%s<ps/>"
"<ps/>"
"The file where the error occurred was:<ps/>"
"%s<ps/>"
"<ps/>"
"This file has been deleted to avoid corrupt data.<ps/>"),
erstr, file);
e_dialog_text_set(dia, buf);
e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
e_dialog_button_focus_num(dia, 0);
elm_win_center(dia->win, 1, 1);
e_object_del_attach_func_set(E_OBJECT(dia),
_e_config_error_dialog_cb_delete);
e_dialog_show(dia);
_e_config_error_dialog = dia;
}
}
return 0;
}
return 1;
}

View File

@ -113,6 +113,7 @@ E_API Eina_Bool stopping = EINA_FALSE;
E_API Eina_Bool restart = EINA_FALSE;
E_API Eina_Bool e_nopause = EINA_FALSE;
E_API Eina_Bool after_restart = EINA_FALSE;
E_API Eina_Bool e_main_loop_running = EINA_FALSE;
EINTERN const char *e_first_frame = NULL;
EINTERN double e_first_frame_start_time = -1;
@ -1087,9 +1088,13 @@ main(int argc, char **argv)
TS("MAIN LOOP AT LAST");
if (!setjmp(x_fatal_buff))
ecore_main_loop_begin();
{
e_main_loop_running = EINA_TRUE;
ecore_main_loop_begin();
}
else
CRI("FATAL: X Died. Connection gone. Abbreviated Shutdown\n");
e_main_loop_running = EINA_FALSE;
inloop = EINA_FALSE;
stopping = EINA_TRUE;