【llbc】App运行时能力扩充 - 只允许启动一个实例 #224#378
Conversation
This reverts commit 71e2a5e.
lailongwei
left a comment
There was a problem hiding this comment.
win32平台实现还有一些细节问题,可以再修改一波,另:建议整体review一下哈 @faith-free1st
| static char runningExeFilePath[MAX_PATH + 1] = {0}; | ||
|
|
||
| // Get current execute file path. | ||
| LLBC_String selfExeFilePath = LLBC_Directory::ModuleFilePath(); |
| NULL); | ||
| LLBC_SetErrAndReturnIf(exclusiveInfoFile == INVALID_HANDLE_VALUE, LLBC_ERROR_OSAPI, LLBC_FAILED); | ||
| LLBC_Defer( | ||
| LLBC_SetErrAndReturnIf(!CloseHandle(exclusiveInfoFile), LLBC_ERROR_OSAPI,) |
There was a problem hiding this comment.
此处是defer语句,无需再set error and return if, 直接CloseHandle()即可
| : exclusiveInfoFilePath; | ||
|
|
||
| // Create then read-lock exclusive info file. | ||
| HANDLE exclusiveInfoFile = CreateFileA(nmlExclusiveInfoFilePath.c_str(), |
There was a problem hiding this comment.
windows操作系统api是大驼峰风格,容易跟框架混淆,建议加'::'
|
|
||
| // Read pid from exclusive info file. | ||
| DWORD fileSize = GetFileSize(exclusiveInfoFile, NULL); | ||
| LLBC_SetErrAndReturnIf(fileSize == -1 || fileSize >= 32, LLBC_ERROR_OSAPI, LLBC_FAILED); |
There was a problem hiding this comment.
未细致考虑:几处问题:
- 返回 -1 是失败的判断,不符合api规范,建议用
INVALID_FILE_SIZE来判断 fileSize >= 32是一个“业务测认为的错误”,在这种情况下,win32 api ::GetLastError()返回的值是错误或不确定的,因为OS API并未失败,这里粗心了,未区分这个case就统一以LLBC_ERROR_OSAPI方式的错误来做GetFileSize()时,如果大小大于32位大小,会将高位设置到第二个参数,我们使用时还很复杂,建议使用GetFileSizeEx()代替,详细api说明(下方也有截图):https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfilesize
| exclusiveInfoBuf[fileSize] = '\0'; | ||
|
|
||
| // Deal with exclusive info. | ||
| long exclusiveInfoPid = LLBC_Str2Long(exclusiveInfoBuf); |
There was a problem hiding this comment.
long在windows msvc的64位模式下编译是32位大小,有歧义(int/long是可以给编译器做决定的,而msvc遵循32位规范来,就是这样奇葩),建议使用int64
|
|
||
| // Deal with exclusive info. | ||
| long exclusiveInfoPid = LLBC_Str2Long(exclusiveInfoBuf); | ||
| LLBC_ReturnIf(exclusiveInfoPid == getpid(), LLBC_FAILED); |
There was a problem hiding this comment.
这里直接返回错误,未设置任何errorno,不符合框架规范:框架api要求统一化设置错误码
| // Open executable process by pid. | ||
| HANDLE processHandle = OpenProcess( | ||
| PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, exclusiveInfoPid); | ||
| if (processHandle != NULL) |
There was a problem hiding this comment.
OpenProcess()应该会返回很多很多种可能性的错误,是否需要针对性处理,而不是统一处理(==null时)
There was a problem hiding this comment.
这里没有对processHandle为NULL的情况做处理主要两点原因:
1、processHandle在一些情况下为NULL是合理的,比如之前运行的进程已经结束了。
2、这里是NULL不会影响后续逻辑,打印一条日志容易产生误会

可选方案
1、文件锁
优点:
标准化实现,可拓展
tip:目录+pid保证原子性,检查/proc//exe来保证进程指向验证
缺点:
crash之后需要额外处理(llbc不做处理,在进程启动的时候会检查并清理文件内容)
2、POSIX named semaphores
优点:
实现简单,原子性保证
缺点:
具有内核生命周期,系统重启前都无法被清理
3、bind a port
优点:
进程关闭后内核会自动关闭文件描述符,简单
如果进程本身就已经绑定了一个port,那完全无需改动
缺点:
对于不需要绑定port的进程也需要绑定port
port是系统资源,对于运行多个程序的系统,依赖单一的port不太可控
文件描述符会传递给子进程
依赖网络功能
4、mutex in shared memory
优点:
基于共享内存,原子性保证
缺点:
crash之后需要额外处理
方案选取
方案2,4在crash之后都会有数据残留且难以清除的问题。
接下来是在方案1和3里面选,两个方案其实都是操作系统提供的支持,一个是文件模块,一个是网络模块。
考虑到网络模块是对外模块,采用使用文件模块的方案一更稳定一些,最终决定使用方案一
功能实现
在Mac和Linux下:
1、判断.pid文件是否存在,存在就读取pid判断是否同名进程运行中。不存在则创建文件并直接进入下一步
2、设置.pid文件锁,写入当前进程pid到.pid文件中,释放文件锁
在Win平台下,步骤2中的设置文件锁,可以通过设置CreateFileA接口参数中的dwShareMode参数为共享读FILE_SHARE_READ只来实现。