如何解决Debian/Ubuntu下ibus-libpinyin时不时卡死无响应问题

故障表现

在Debian 12和Ubuntu 22.04及以上版本中,如果使用ibus-libpinyin,会有概率性出现输入卡死无响应,等待一段时间才会有文字输出。

ibus-libpinyin上面也有人提了这个issue:https://github.com/libpinyin/ibus-libpinyin/issues/429

解决方法

用如下命令,删除当前登录用户下ibus-libpinyin产生的所有.tmp缓存文件:

rm -rf ~/.cache/ibus/libpinyin/*.tmp

如果有兴趣知道问题根源的话,可以继续阅读。

排查过程

从strace ibus-engine-libpinyin进程发现,似乎是一些__db.*.tmp文件导致的:

unlink("/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp") = -1 ENOENT (No such file or directory)
unlink("/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp") = -1 ENOENT (No such file or directory)
getpid()                                = 29864
openat(AT_FDCWD, "/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 18
read(18, "0-15\n", 1024)                = 5
close(18)                               = 0
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
newfstatat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/user_pinyin_index.bin.tmp", 0xxxxxxxxxxxxx, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/devreporter/.cache/ibus/libpinyin/__db.user_pinyin_index.bin.tmp", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EEXIST (File exists)
pselect6(0, NULL, NULL, NULL, {tv_sec=1, tv_nsec=1000}, NULL) = 0 (Timeout)
//......()
write(2, "BDB0002 __fop_file_setup:  Retry"..., 53) = 53
write(2, "\n", 1)                       = 1

找到gnu出处,是这么说的:

If both O_CREAT and O_EXCL are set, then open fails if the specified file already exists. This is guaranteed to never clobber an existing file.

再细看~/.cache/ibus/libpinyin目录,这里面的文件貌似是Berkeley DB,那么这些__db*.tmp文件怎样来的呢?

阅读libpinyin库源代码后,发现“.tmp”后缀是这么来的,先看pinyin.cpp:

struct _pinyin_context_t{
    //忽略
    /* default tables. */
    FacadeChewingTable2 * m_pinyin_table;
    //忽略
};

bool pinyin_save(pinyin_context_t * context){
    //忽略
    bool retval = _write_files(context) && _rename_files(context);
    //忽略
}

static bool _write_files(pinyin_context_t * context){
    //忽略
    /* save user pinyin table */
    gchar * tmpfilename = g_build_filename
        (context->m_user_dir, USER_PINYIN_INDEX ".tmp", NULL);
    unlink(tmpfilename);

    context->m_pinyin_table->store(tmpfilename);

    g_free(tmpfilename);

    //忽略

}

“context->m_pinyin_table->store(tmpfilename)”调用的是FacadeChewingTable2::store方法,在文件storage/facade_chewing_table2.h:

class FacadeChewingTable2{
private:
    //忽略
    ChewingLargeTable2 * m_user_chewing_table;
    //忽略
    bool store(const char * new_user_filename) {
        if (NULL == m_user_chewing_table)
            return false;
        return m_user_chewing_table->store_db(new_user_filename);
    }
    //忽略
}

属性m_user_chewing_table的ChewingLargeTable2是在storage/chewing_large_table2.h中定义的:

#ifdef HAVE_BERKELEY_DB
#include "chewing_large_table2_bdb.h"
#endif

#ifdef HAVE_KYOTO_CABINET
#include "chewing_large_table2_kyotodb.h"
#endif

因为确定是Berkeley DB,那么直接看storage/chewing_large_table2_bdb.cpp中的store_db方法即可:

bool ChewingLargeTable2::store_db(const char * new_filename) {
    DB * tmp_db = NULL;

    int ret = unlink(new_filename);
    if (ret != 0 && errno != ENOENT)
        return false;

    ret = db_create(&tmp_db, NULL, 0);
    assert(0 == ret);

    if (NULL == tmp_db)
        return false;

    ret = tmp_db->open(tmp_db, NULL, new_filename, NULL,
                       DB_BTREE, DB_CREATE, 0600);
    if (ret != 0)
        return false;

    if (!copy_bdb(m_db, tmp_db))
        return false;

    if (tmp_db != NULL) {
        tmp_db->sync(m_db, 0);
        tmp_db->close(tmp_db, 0);
    }

    return true;
}

从strace情况来看,应该是“tmp_db->open”出故障了,那“__db.”又是哪里来的呢?Oracle论坛里面有人问类似问题【2】,有人给了个答案:“Shared memory regions”【3】,但文档里面说的是DB_ENV,感觉和这个问题无关:

Shared memory regions:…… Any files created in the filesystem to back the regions are created in the environment home directory specified to the DB_ENV->open() call. These files are named __db.### (for example, __db.001, __db.002 and so on). 

但无论如何,问题确定了,用户缓存目录“【用户home目录,比如/home/xxxxx】/.cache/ibus/libpinyin”下面意外存在了各种.tmp文件,然后阻塞了后续读写流程。

参考文档

【1】https://www.gnu.org/software/libc/manual/html_node/Open_002dtime-Flags.html

【2】https://forums.oracle.com/ords/apexds/post/what-is-db-file-7859

【3】https://docs.oracle.com/cd/E17275_01/html/programmer_reference/env_region.html

本页永久链接:https://www.orztip.com/?p=887&article_title=ibus-libpinyin-unresponsive