注:本日志创作时,日志系统已经完成开发和调试,本日志为补档。
锁机制在涉及到资源管理的系统中,尤其是涉及到多进程、多任务等需求的系统中尤为常见,主要是确保资源处理单位在其任务周期内对资源的独占。
在 Seafile 中,由于支持文件分享,且支持文件的在线编辑,那么就必然会涉及到一个文件被多人同时修改的多人协作情况。为了防止多人同时修改文件导致文件内容版本出现错误,Seafile Pro 版本设计了一个“锁”系统,能够允许用户在需要进行文件修改的时候先把文件“锁”起来,编辑完以后再释放锁,保证同一时间仅有一个用户编辑文件。
其实,Seafile 社区版就是从 Pro 版阉割下来的。尤其是 Seahub 中,许多 Pro 版本相关的功能界面仅仅是通过一个 is_pro()
函数判断的,这给我们的开发带来了不少便利。
根据研究、推论可以总结出,锁机制在 Seafile Pro 中实现方式和步骤:
确认文件锁状态
获取文件锁信息
锁定文件
解锁文件
在 Seahub 中,几乎所有与锁机制相关的代码全部都使用 is_pro()
函数进行了限制。不过,在 Seafile-server,也就是这个服务实现的底层就完全不一样了。全文搜索 is_pro
,甚至没有发现任何相似的字眼,看来是把后端代码删的一干二净了。那么,要想把原本 pro 中的锁机制恢复出来,主要有以下几个任务:
python/seaserv
的 api.py
中定义的 RPC API;要想恢复 Pro 的功能,还得从 Pro 下手,欸嘿。
在前面的研究中,我们知道,Seafile-server 和 Seahub 之间的连接是通过 Searpc 来实现的。这个连接有个特点,就是需要一个 Python 的 api 文件,为对应的 Python RPC 客户端提供可用的函数列表。那么就意味着,这个接口文件一定是未经加密、直接以 Python 源代码的形式出现的。
在 Seafile-pro 的 Docker 控制台中运行如下命令:
find / -name api.py
果不其然,得到了系统的输出:
/opt/seafile/seafile-pro-server-7.1.14/seahub/thirdpart/requests/api.py
/opt/seafile/seafile-pro-server-7.1.14/seahub/thirdpart/django/contrib/messages/api.py
/opt/seafile/seafile-pro-server-7.1.14/seafile/lib/python3.6/site-packages/seaserv/api.py
/usr/local/lib/python3.6/dist-packages/cffi/api.py
/usr/local/lib/python3.6/dist-packages/aliyunsdkcore/vendored/requests/api.py
/usr/local/lib/python3.6/dist-packages/requests/api.py
/usr/local/lib/python3.6/dist-packages/oss2/api.py
/usr/local/lib/python3.6/dist-packages/sqlalchemy/ext/declarative/api.py
/usr/local/lib/python3.6/dist-packages/sqlalchemy/event/api.py
/usr/local/lib/python3.6/dist-packages/django/contrib/messages/api.py
/usr/local/lib/python3.6/dist-packages/pip/_vendor/requests/api.py
通过对社区版的了解,我们知道,/opt/seafile/seafile-pro-server-7.1.14/seafile/lib/python3.6/site-packages/seaserv/api.py
中的内容是最有可能的。vim 打开这个文件,果然发现了蕴藏在其中的接口定义:
# file lock
def get_locked_files(self, repo_id):
"""
Return a list of FileLock objects (lib/repo.vala)
"""
return seafserv_threaded_rpc.get_locked_files(repo_id)
def lock_file(self, repo_id, path, user, expire):
return seafserv_threaded_rpc.lock_file(repo_id, path, user, expire)
def unlock_file(self, repo_id, path):
return seafserv_threaded_rpc.unlock_file(repo_id, path)
FILE_LOCKED_BY_OTHERS = 1
FILE_LOCKED_BY_ME = 2
def check_file_lock(self, repo_id, path, user):
"""
Returns:
* 0: if file is not locked
* 1: if file is locked by others
* 2: if file is locked by me
"""
return seafserv_threaded_rpc.check_file_lock(repo_id, path, user)
def refresh_file_lock(self, repo_id, path):
"""
Returns:
* 0: success
* -1: error
* -2: the file is not locked
"""
return seafserv_threaded_rpc.refresh_file_lock(repo_id, path)
def get_lock_info(self, repo_id, path):
"""
Return filelock object
"""
return seafserv_threaded_rpc.get_lock_info(repo_id, path);
这就为我们的工作带来了太多便利,因为我们已经知道了要实现锁机制所需要的全部函数。
回到我们的工作目录,打开seaserv/api.py
:
# file lock
def check_file_lock(self, repo_id, path, user):
"""
Always return 0 since CE doesn't support file locking.
"""
return 0
只剩一个 check_file_lock
函数凄惨地矗立在那里,我们把刚才在 Pro 里发现的东西全部都拿来。
俏皮一下:
def check_file_lock(self, repo_id, path, user):
"""
Always return 0 since CE doesn't support file locking.
No, not anymore! <---- HERE
Returns:
0: file is not locked.
1: file is locked by others.
2: file is locked by me.
"""
return seafserv_threaded_rpc.check_file_lock(repo_id, path, user)
根据前面的代码,我们需要在 seafserv_threaded_rpc
模块中把对应的 RPC 函数调用过程全部推理恢复出来,在文件seafile/rpcclient.py
中:
# file lock
@searpc_func("objlist", ["string"])
def seafile_get_locked_files(repo_id):
pass
get_locked_files = seafile_get_locked_files
@searpc_func("int", ["string", "string", "string", "int64"])
def seafile_lock_file(repo_id, path, user, expire):
pass
lock_file = seafile_lock_file
#后面的不再展示了
根据我们先前对 Searpc 的研究,要想在 C 代码中注册这些函数,必须确保函数的参数签名在文件 rpc_table.py
文件中列出。
根据检查,我们需要在 rpc_table.py
中添加以下条目:
[ "int", ["string", "string", "string", "int64"] ]
代表的是,int
类型返回值,前三个参数为 string
,最后一个参数为 gint64
,对应的是 seafile_lock_file
函数。
然后在我们的 Docker 容器中运行以下命令:
make clean
make
make install
Seafile-server 的 Makefile 文件中有相应的命令根据 rpc_table.py
文件生成 C 语言的签名。
然后,根据刚才的 api 文件,我们可以比较轻松的把六个C语言的函数注册写出来:
/* file lock */
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_get_locked_files,
"seafile_get_locked_files",
searpc_signature_objlist__string());
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_lock_file,
"seafile_lock_file",
searpc_signature_int__string_string_string_int64());
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_unlock_file,
"seafile_unlock_file",
searpc_signature_int__string_string());
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_check_file_lock,
"seafile_check_file_lock",
searpc_signature_int__string_string_string());
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_refresh_file_lock,
"refresh_file_lock",
searpc_signature_int__string_string());
searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_get_lock_info,
"get_lock_info",
searpc_signature_object__string_string());
现在,RPC 相关的东西都定义好了,我们把这里面的 C 函数都实现一个最简单的功能:打印日志。
以锁文件为例:
int
seafile_lock_file (const char *repo_id,
const char *path,
const char *user,
gint64 expire)
{
/*
* Locks the file for specific user.
* Returns:
* 0: Lock success.
* -1: File already locked, you cannot lock it.
* -2: Permission denied.
*/
seaf_message("-->Lock file(%s, %s, %s, %d)\n", repo_id, path, user, expire);
return 0;
}
就这样,给六个文件全部先实现一个最简单的打印日志函数,编译后运行服务端。
这一步比较简单,前面说过,前端大部分与锁相关的代码根本没有被去除,而只是用了 is_pro()
去限制。那么,我们只要在 Seahub 项目中全局查找 “is_pro()”,并把我们认为的与锁相关的全部代码对 pro 判断的语句全部去除就好啦!
在这里就不对它做详细介绍了。
当然,虽然这一步听起来很简单,但是实际上还是有一定难度的,用到 is_pro()
语句的地方多达几百处。
然后,解除了前端的 is_pro()
限制,在前端已经可以看到锁按钮了。
单击锁按钮,在控制台中输出了信息:
-->Check file lock(34151e41-f5bf-4477-aa4b-46b0c4eaafb5, /NewFile.txt, rentenglong@163.com)
-->Lock file(34151e41-f5bf-4477-aa4b-46b0c4eaafb5, /NewFile.txt, rentenglong@163.com, 0)
即证明,前后端的 RPC 交互已经完成,前后的 RPC 系统完全正常。接下来,我们只要把 C 代码里面定义的函数实现了,就万事大吉了。