Преглед изворни кода

ExternalProject: add INACTIVITY_TIMEOUT argument

In order to abort transfers on slow connections the ExternalProject
command support passing the INACTIVITY_TIMEOUT argument.

Fixes: #20992
Thomas Bernard пре 5 година
родитељ
комит
116b06870d

+ 3 - 1
Modules/ExternalProject-download.cmake.in

@@ -105,7 +105,8 @@ set(retry_number 5)
 
 message(STATUS "Downloading...
    dst='@LOCAL@'
-   timeout='@TIMEOUT_MSG@'"
+   timeout='@TIMEOUT_MSG@'
+   inactivity timeout='@INACTIVITY_TIMEOUT_MSG@'"
 )
 set(download_retry_codes 7 6 8 15)
 set(skip_url_list)
@@ -128,6 +129,7 @@ foreach(i RANGE ${retry_number})
         "${url}" "@LOCAL@"
         @SHOW_PROGRESS@
         @TIMEOUT_ARGS@
+        @INACTIVITY_TIMEOUT_ARGS@
         STATUS status
         LOG log
         @USERPWD_ARGS@

+ 14 - 2
Modules/ExternalProject.cmake

@@ -179,6 +179,9 @@ External Project Definition
       ``TIMEOUT <seconds>``
         Maximum time allowed for file download operations.
 
+      ``INACTIVITY_TIMEOUT <seconds>``
+        Terminate the operation after a period of inactivity.
+
       ``HTTP_USERNAME <username>``
         Username for the download operation if authentication is required.
 
@@ -1300,7 +1303,7 @@ function(_ep_write_gitupdate_script script_filename git_EXECUTABLE git_tag git_r
   )
 endfunction()
 
-function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout no_progress hash tls_verify tls_cainfo userpwd http_headers netrc netrc_file)
+function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout inactivity_timeout no_progress hash tls_verify tls_cainfo userpwd http_headers netrc netrc_file)
   if(timeout)
     set(TIMEOUT_ARGS TIMEOUT ${timeout})
     set(TIMEOUT_MSG "${timeout} seconds")
@@ -1308,6 +1311,14 @@ function(_ep_write_downloadfile_script script_filename REMOTE LOCAL timeout no_p
     set(TIMEOUT_ARGS "# no TIMEOUT")
     set(TIMEOUT_MSG "none")
   endif()
+  if(inactivity_timeout)
+    set(INACTIVITY_TIMEOUT_ARGS INACTIVITY_TIMEOUT ${inactivity_timeout})
+    set(INACTIVITY_TIMEOUT_MSG "${inactivity_timeout} seconds")
+  else()
+    set(INACTIVITY_TIMEOUT_ARGS "# no INACTIVITY_TIMEOUT")
+    set(INACTIVITY_TIMEOUT_MSG "none")
+  endif()
+
 
   if(no_progress)
     set(SHOW_PROGRESS "")
@@ -2512,6 +2523,7 @@ function(_ep_add_download_command name)
         string(REPLACE ";" "-" fname "${fname}")
         set(file ${download_dir}/${fname})
         get_property(timeout TARGET ${name} PROPERTY _EP_TIMEOUT)
+        get_property(inactivity_timeout TARGET ${name} PROPERTY _EP_INACTIVITY_TIMEOUT)
         get_property(no_progress TARGET ${name} PROPERTY _EP_DOWNLOAD_NO_PROGRESS)
         get_property(tls_verify TARGET ${name} PROPERTY _EP_TLS_VERIFY)
         get_property(tls_cainfo TARGET ${name} PROPERTY _EP_TLS_CAINFO)
@@ -2521,7 +2533,7 @@ function(_ep_add_download_command name)
         get_property(http_password TARGET ${name} PROPERTY _EP_HTTP_PASSWORD)
         get_property(http_headers TARGET ${name} PROPERTY _EP_HTTP_HEADER)
         set(download_script "${stamp_dir}/download-${name}.cmake")
-        _ep_write_downloadfile_script("${download_script}" "${url}" "${file}" "${timeout}" "${no_progress}" "${hash}" "${tls_verify}" "${tls_cainfo}" "${http_username}:${http_password}" "${http_headers}" "${netrc}" "${netrc_file}")
+        _ep_write_downloadfile_script("${download_script}" "${url}" "${file}" "${timeout}" "${inactivity_timeout}" "${no_progress}" "${hash}" "${tls_verify}" "${tls_cainfo}" "${http_username}:${http_password}" "${http_headers}" "${netrc}" "${netrc_file}")
         set(cmd ${CMAKE_COMMAND} -P "${download_script}"
           COMMAND)
         if (no_extract)

+ 5 - 0
Tests/RunCMake/ExternalProject/DownloadInactivityResume.cmake

@@ -0,0 +1,5 @@
+include(ExternalProject)
+ExternalProject_Add(MyProj URL ${SERVER_URL} INACTIVITY_TIMEOUT 2 DOWNLOAD_NO_EXTRACT TRUE
+  CONFIGURE_COMMAND ""
+  BUILD_COMMAND ""
+  INSTALL_COMMAND "")

+ 1 - 0
Tests/RunCMake/ExternalProject/DownloadInactivityTimeout-build-result.txt

@@ -0,0 +1 @@
+[^0]

+ 1 - 0
Tests/RunCMake/ExternalProject/DownloadInactivityTimeout-build-stdout.txt

@@ -0,0 +1 @@
+(Timeout was reached)?

+ 5 - 0
Tests/RunCMake/ExternalProject/DownloadInactivityTimeout.cmake

@@ -0,0 +1,5 @@
+include(ExternalProject)
+ExternalProject_Add(MyProj URL ${SERVER_URL} INACTIVITY_TIMEOUT 2 DOWNLOAD_NO_EXTRACT TRUE
+  CONFIGURE_COMMAND ""
+  BUILD_COMMAND ""
+  INSTALL_COMMAND "")

+ 42 - 0
Tests/RunCMake/ExternalProject/DownloadServer.py

@@ -0,0 +1,42 @@
+from http.server import HTTPServer, BaseHTTPRequestHandler
+import argparse
+import time
+import subprocess
+import sys
+import os
+args = None
+outerthread = None
+
+class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
+    def do_GET(self):
+        self.send_response(200)
+        self.end_headers()
+        data = b'D'
+
+        if args.speed_limit:
+            slow_deadline = time.time()+args.limit_duration
+
+            while time.time() < slow_deadline:
+                self.wfile.write(data)
+                if args.speed_limit:
+                    time.sleep(1.1)
+
+        data = data * 100
+        self.wfile.write(data)
+        self.close_connection = True
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--speed_limit', help='transfer rate limitation', action='store_true',default=False)
+    parser.add_argument('--limit_duration', help='duration of the transfer rate limitation',default=1, type=float)
+    parser.add_argument('--file', help='file to write the url to connect to')
+    parser.add_argument('--subprocess', action='store_true')
+    args = parser.parse_args()
+    if not args.subprocess:
+        subprocess.Popen([sys.executable]+sys.argv+['--subprocess'],stdin=subprocess.DEVNULL, stderr=subprocess.DEVNULL,stdout=subprocess.DEVNULL)
+    else:
+        httpd = HTTPServer(('localhost', 0), SimpleHTTPRequestHandler)
+        with open(args.file,"w") as f:
+            f.write('http://localhost:{}/test'.format(httpd.socket.getsockname()[1]))
+        httpd.handle_request()
+        os.remove(args.file)

+ 52 - 0
Tests/RunCMake/ExternalProject/RunCMakeTest.cmake

@@ -1,5 +1,11 @@
+cmake_minimum_required(VERSION 3.12)
 include(RunCMake)
 
+# We do not contact any remote URLs, but may use a local one.
+# Remove any proxy configuration that may change behavior.
+unset(ENV{http_proxy})
+unset(ENV{https_proxy})
+
 run_cmake(IncludeScope-Add)
 run_cmake(IncludeScope-Add_Step)
 run_cmake(NoOptions)
@@ -27,6 +33,50 @@ function(__ep_test_with_build testName)
   run_cmake_command(${testName}-build ${CMAKE_COMMAND} --build .)
 endfunction()
 
+find_package(Python3)
+function(__ep_test_with_build_with_server testName)
+  if(NOT Python3_EXECUTABLE)
+    return()
+  endif()
+  set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${testName}-build)
+  set(RunCMake_TEST_NO_CLEAN 1)
+  set(RunCMake_TEST_TIMEOUT 20)
+  set(RunCMake_TEST_OUTPUT_MERGE TRUE)
+  file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
+  file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
+  set(URL_FILE ${RunCMake_BINARY_DIR}/${testName}.url)
+  if(EXISTS "${URL_FILE}")
+    file(REMOVE "${URL_FILE}")
+  endif()
+  execute_process(
+    COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/DownloadServer.py --file "${URL_FILE}" ${ARGN}
+    OUTPUT_FILE ${RunCMake_BINARY_DIR}/${testName}-python.txt
+    ERROR_FILE ${RunCMake_BINARY_DIR}/${testName}-python.txt
+    RESULT_VARIABLE result
+    TIMEOUT 30
+    )
+  if(NOT result EQUAL 0)
+    message(FATAL_ERROR "Failed to start download server:\n  ${result}")
+  endif()
+
+  foreach(i RANGE 1 8)
+    if(EXISTS ${URL_FILE})
+      break()
+    endif()
+    execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${i})
+  endforeach()
+
+  if(NOT EXISTS ${URL_FILE})
+    message(FATAL_ERROR "Failed to load download server URL from:\n  ${URL_FILE}")
+  endif()
+
+  file(READ ${URL_FILE} SERVER_URL)
+  message(STATUS "URL : ${URL_FILE} - ${SERVER_URL}")
+  run_cmake_with_options(${testName} ${CMAKE_COMMAND} -DSERVER_URL=${SERVER_URL} )
+  run_cmake_command(${testName}-clean ${CMAKE_COMMAND} --build . --target clean)
+  run_cmake_command(${testName}-build ${CMAKE_COMMAND} --build .)
+endfunction()
+
 __ep_test_with_build(MultiCommand)
 
 set(RunCMake_TEST_OUTPUT_MERGE 1)
@@ -39,6 +89,8 @@ if(NOT RunCMake_GENERATOR MATCHES "Visual Studio")
   __ep_test_with_build(LogOutputOnFailure)
   __ep_test_with_build(LogOutputOnFailureMerged)
   __ep_test_with_build(DownloadTimeout)
+  __ep_test_with_build_with_server(DownloadInactivityTimeout --speed_limit --limit_duration 40)
+  __ep_test_with_build_with_server(DownloadInactivityResume --speed_limit --limit_duration 1)
 endif()
 
 # We can't test the substitution when using the old MSYS due to