Berkeley DB: 在c api调用中异常退出导致卡在 futex_wait 调用
C开发,使用berkeley db 4.3 (/usr/lib64/libdb-4.3.so) on RHEL5.6 with kernel 2.6.18-238_xen_AMD64.
在我的测试中 (写入 1,000,000 key/value pairs), 如果某个进程在对db进行操作的过程中异常退出(ctrl + c, kill, or assert fails) , 此后对db的打开都会被block. 使用strace可以看出,进程卡在了一个 futex(ptr_to_something, FUTEX_WAIT, 2, NULL) 调用。之前它刚打开 __db.00x(e.g __db.001, __db.002, __db.003) .
目前我所知的唯一方法就是删掉 __db.00x 文件,后续测试表明数据库并未损坏,仍可以正常读写。这个出方法基本可以满足我的需求,但是我想知道是否有更好、或者说更优雅的处理方案。
下面列出一些strace的输出,以及用户操作db的代码,也许会有帮助。
some of the strace stderr
... open("__db.001", O_RDWR) = 3 fcntl(3, F_SETFD, FD_CLOEXEC) = 0 fstat(3, {st_mode=S_IFREG|0640, st_size=24576, ...}) = 0 close(3) = 0 open("__db.001", O_RDWR) = 3 fcntl(3, F_SETFD, FD_CLOEXEC) = 0 mmap(NULL, 24576, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0x2afcc4149000 close(3) = 0 futex(0x2afcc4149000, FUTEX_WAIT, 2, NULL **[[stuck here]]**
code to operate the database
typedef DB* db_handle; db_handle bdb_open(const char *filename, u_int32_t cache_size_mb) { int ret; DB_ENV *env; db_handle dbp; u_int32_t flags = DB_CREATE | DB_THREAD | DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_LOCK ; u_int32_t gb = cache_size_mb / 1024, mb = cache_size_mb % 1024; if (ret = db_env_create(&env, 0)) { fprintf(stderr, "db_env_create:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if (ret = env->set_timeout(env, 3 * 1000000, DB_SET_LOCK_TIMEOUT)) { fprintf(stderr, "env->set_timeout:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if (ret = env->set_lk_detect(env, DB_LOCK_DEFAULT)) { /* this seems to be of no use in my case */ fprintf(stderr, "env->set_lk_detect:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if (ret = env->set_cachesize(env, gb, mb * 1024 * 1024, 0)) { fprintf(stderr, "env->set_cachesize:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if ((ret = env->open(env, NULL, flags, 0)) != 0) { fprintf(stderr, "db_env_open:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if (ret = db_create(&dbp, env, 0)) { fprintf(stderr, "db_create:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } if (ret = dbp->open(dbp, NULL, filename, NULL, DB_BTREE, flags, 0664)) { fprintf(stderr, "dbp->open:%d, %s\n", ret, db_strerror(ret)); exit(EXIT_FAILURE); } return dbp; } int bdb_put(db_handle db, void* key, u_int32_t keylen, void* val, u_int32_t vallen) { DBT dkey, dval; bzero(&dkey, sizeof(dkey)); bzero(&dval, sizeof(dval)); dkey.data = key, dkey.size = keylen; dval.data = val, dval.size = vallen; return db->put(db, NULL, &dkey, &dval, 0); } int bdb_get(db_handle db, void* key, const u_int32_t keylen, void* buf, u_int32_t buflen, u_int32_t* nwrite) { DBT dkey, dval; bzero(&dkey, sizeof(dkey)); bzero(&dval, sizeof(dval)); dkey.data = key, dkey.size = keylen; dval.data = buf, dval.ulen = buflen, dval.flags = DB_DBT_USERMEM; int ret = db->get(db, NULL, &dkey, &dval, 0); if (ret == 0 && nwrite != NULL) *nwrite = dval.size; return ret; }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
晾了两个月,自己了结一下吧。貌似无解。
根据官方文档FAQ 第一个问题里给出的第一个链接:
也就是说,正在操作它的某个进程挂掉以后,数据库可能是处于不可用状态,必须启动一个修复进程。
多线程的操作也可能导致类似的问题,但是不一定需要重启整个进程,上面的几个链接中也有具体的说明。
因此综合来看,BerkeleyDB并不适合多进程读写,最好是只有一个进程进行读写,然后对外提供接口,例如memcachedb。
感谢 xinzsky 对早期某个版本FAQ的 翻译。
因为BDB是嵌入式的数据库操作,多个进程使用就相当于每个进程的代码都要存在一整套BDB的操作,错误处理代码,这个对事务性操作尤其重要。