|
8 | 8 |
|
9 | 9 | import pexpect
|
10 | 10 | import tornado
|
| 11 | +import tornado.locks |
| 12 | +import datetime |
11 | 13 |
|
12 | 14 |
|
13 | 15 | # Git configuration options exposed through the REST API
|
|
16 | 18 | # See https://git-scm.com/docs/git-config#_syntax for git var syntax
|
17 | 19 | CONFIG_PATTERN = re.compile(r"(?:^|\n)([\w\-\.]+)\=")
|
18 | 20 | DEFAULT_REMOTE_NAME = "origin"
|
| 21 | +# How long to wait to be executed or finished your execution before timing out |
| 22 | +MAX_WAIT_FOR_EXECUTE_S = 20 |
| 23 | +# Ensure on NFS or similar, that we give the .git/index.lock time to be removed |
| 24 | +MAX_WAIT_FOR_LOCK_S = 5 |
| 25 | +# How often should we check for the lock above to be free? This comes up more on things like NFS |
| 26 | +CHECK_LOCK_INTERVAL_S = 0.1 |
19 | 27 |
|
| 28 | +execution_lock = tornado.locks.Lock() |
20 | 29 |
|
21 | 30 | async def execute(
|
22 | 31 | cmdline: "List[str]",
|
23 |
| - cwd: "Optional[str]" = None, |
| 32 | + cwd: "str", |
24 | 33 | env: "Optional[Dict[str, str]]" = None,
|
25 | 34 | username: "Optional[str]" = None,
|
26 | 35 | password: "Optional[str]" = None,
|
@@ -85,19 +94,36 @@ def call_subprocess(
|
85 | 94 | output, error = process.communicate()
|
86 | 95 | return (process.returncode, output.decode("utf-8"), error.decode("utf-8"))
|
87 | 96 |
|
88 |
| - if username is not None and password is not None: |
89 |
| - code, output, error = await call_subprocess_with_authentication( |
90 |
| - cmdline, |
91 |
| - username, |
92 |
| - password, |
93 |
| - cwd, |
94 |
| - env, |
95 |
| - ) |
96 |
| - else: |
97 |
| - current_loop = tornado.ioloop.IOLoop.current() |
98 |
| - code, output, error = await current_loop.run_in_executor( |
99 |
| - None, call_subprocess, cmdline, cwd, env |
100 |
| - ) |
| 97 | + try: |
| 98 | + await execution_lock.acquire(timeout=datetime.timedelta(seconds=MAX_WAIT_FOR_EXECUTE_S)) |
| 99 | + except tornado.util.TimeoutError: |
| 100 | + return (1, "", "Unable to get the lock on the directory") |
| 101 | + |
| 102 | + try: |
| 103 | + # Ensure our execution operation will succeed by first checking and waiting for the lock to be removed |
| 104 | + time_slept = 0 |
| 105 | + lockfile = os.path.join(cwd, '.git', 'index.lock') |
| 106 | + while os.path.exists(lockfile) and time_slept < MAX_WAIT_FOR_LOCK_S: |
| 107 | + await tornado.gen.sleep(CHECK_LOCK_INTERVAL_S) |
| 108 | + time_slept += CHECK_LOCK_INTERVAL_S |
| 109 | + |
| 110 | + # If the lock still exists at this point, we will likely fail anyway, but let's try anyway |
| 111 | + |
| 112 | + if username is not None and password is not None: |
| 113 | + code, output, error = await call_subprocess_with_authentication( |
| 114 | + cmdline, |
| 115 | + username, |
| 116 | + password, |
| 117 | + cwd, |
| 118 | + env, |
| 119 | + ) |
| 120 | + else: |
| 121 | + current_loop = tornado.ioloop.IOLoop.current() |
| 122 | + code, output, error = await current_loop.run_in_executor( |
| 123 | + None, call_subprocess, cmdline, cwd, env |
| 124 | + ) |
| 125 | + finally: |
| 126 | + execution_lock.release() |
101 | 127 |
|
102 | 128 | return code, output, error
|
103 | 129 |
|
@@ -1003,7 +1029,7 @@ async def diff_content(self, filename, prev_ref, curr_ref, top_repo_path):
|
1003 | 1029 | is_binary = await self._is_binary(filename, "INDEX", top_repo_path)
|
1004 | 1030 | if is_binary:
|
1005 | 1031 | raise tornado.web.HTTPError(log_message="Error occurred while executing command to retrieve plaintext diff as file is not UTF-8.")
|
1006 |
| - |
| 1032 | + |
1007 | 1033 | curr_content = await self.show(filename, "", top_repo_path)
|
1008 | 1034 | else:
|
1009 | 1035 | raise tornado.web.HTTPError(
|
|
0 commit comments