Flask请求中subprocess.Popen指定cwd并发场景偶发路径不存在异常求助
问题根因分析
你的问题核心是相对路径依赖进程全局工作目录 + Flask多线程并发下工作目录被篡改导致的,具体触发逻辑如下:
- Flask默认以多线程模式处理请求,进程的当前工作目录是所有线程共享的全局变量
- 你的
_update是耗时逻辑,大概率内部存在os.chdir调用(比如生成zip包时切换到输出目录、操作临时文件时切换目录等) - 并发请求时,A请求执行到
_update内部切换了工作目录,此时B请求刚好执行到subprocess.Popen步骤,传入的是相对路径./../../../client-repository,基于被篡改后的工作目录计算出来的路径自然不存在,就会抛出No such file or directory错误 - 你打印
repository_path的时候工作目录还没被修改,所以日志输出的路径是符合预期的,打印后到Popen执行前的窗口刚好被另一个线程修改了工作目录,才会出现日志和报错不一致的情况
解决方案
1. 优先将所有仓库路径替换为绝对路径(最高优先级,可解决90%以上该类问题)
在服务初始化、Unit对象赋值repository_path阶段,直接将相对路径转换为绝对路径存储,完全不依赖进程当前工作目录:
import os # 赋值时提前转绝对路径 unit.repository_path = os.path.abspath("./../../../client-repository")
如果用pathlib的话可以用Path("./../../../client-repository").resolve()处理,效果一致。
2. 避免在业务逻辑中随意切换全局工作目录
如果确实需要临时切换目录,使用上下文管理器保证执行完立刻恢复原目录,且如果并发高的场景下需要加线程锁保证目录切换操作互斥:
import os from threading import Lock chdir_lock = Lock() def safe_chdir(target_path): origin_path = os.getcwd() os.chdir(target_path) try: yield finally: os.chdir(origin_path) # 业务中使用时加锁 with chdir_lock, safe_chdir("/path/to/zip/output"): # 执行生成zip等需要在指定目录执行的逻辑 pass
3. 优化Popen写法避免潜在问题
你当前的Popen写法只消费stdout,未处理stderr,当git命令报错输出到stderr时缓冲区满会导致子进程挂死,建议修改为用communicate读取所有输出:
def get_latest_version(unit: Unit): repository_path = unit.repository_path branch = unit.branch print(f'repository_path: {repository_path}') try: with subprocess.Popen(["git", "rev-parse", branch], cwd=repository_path, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: stdout, stderr = p.communicate() if p.returncode == 0: return stdout.strip() # 可打印stderr方便排查git本身的错误 print(f"git command error: {stderr}") except Exception as e: print(f'Exception in popen: {e}') return None
4. 可选优化:添加版本结果缓存
如果允许返回几秒内的版本结果,可给get_latest_version加过期缓存,减少git命令调用次数,既提升性能也降低并发冲突概率。
内容的提问来源于stack exchange,提问作者IgorZ
相关产品推荐
相关产品推荐

