2014-07-02 20 views
35

Bộ kiểm tra pip sử dụng các cuộc gọi phụ để chạy thử nghiệm tích hợp. Gần đây, một PR được đặt để loại bỏ một số mã tương thích cũ hơn. Cụ thể, nó thay thế một hàm b() với việc sử dụng rõ ràng các chữ số b"". Tuy nhiên điều này dường như đã phá vỡ một cái gì đó để một cuộc gọi subprocess cụ thể sẽ treo mãi mãi. Để làm cho vấn đề tồi tệ hơn nó chỉ treo mãi mãi trên Python 3.3 (có lẽ chỉ Python 3.3.5) và nó không thể dễ dàng được sao chép bên ngoài của Travis.Chương trình Python bị treo mãi mãi khi được gọi từ quy trình con

có liên quan yêu cầu Pull:

Một vấn đề tương tự xảy ra với yêu cầu Kéo khác, tuy nhiên họ thất bại trên các phiên bản khác nhau của Python và các trường hợp thử nghiệm khác nhau . Những yêu cầu Pull là:

Một người dùng khác đã báo cáo một vấn đề tương tự với tôi hôm nay tại IRC, họ nói rằng họ có thể tái tạo nó cục bộ trên Ubuntu 14.04 với Python 3.3 từ deadsnakes (nhưng không phải trên OSX) và không chỉ trên Travis như tôi đã chủ yếu là có thể quá cho đến nay. Họ đã gửi cho tôi các bước để tái tạo đó là:

$ git clone [email protected]:xavfernandez/pip.git 
$ cd pip 
$ git checkout debug_stuck 
$ pip install pytest==2.5.2 scripttest==1.3 virtualenv==1.11.6 mock==1.0.1 pretend==1.0.8 setuptools==4.0 
$ # The below should pass just fine 
$ py.test -k test_env_vars_override_config_file -v -s 
$ # Now edit pip/req/req_set.py and remove method remove_me_to_block or change its content to print('KO') or pass 
$ # The below should hang forever 
$ py.test -k test_env_vars_override_config_file -v -s 

Trong ví dụ trên, phương pháp remove_me_to_block không được gọi bất cứ nơi nào, chỉ đơn thuần tồn tại của nó là đủ để làm bài kiểm tra không chặn, và sự tồn tại phi của nó (hoặc thay đổi nội dung của nó) là đủ để làm cho khối thử nghiệm mãi mãi.

Hầu hết các lỗi đã được thực hiện với các thay đổi trong PR này (https://github.com/pypa/pip/pull/1901). Sau khi đã đẩy một lần cam kết tại một thời điểm các thử nghiệm được chuyển cho đến khi cam kết cụ thể này được áp dụng - https://github.com/dstufft/pip/commit/d296df620916b4cd2379d9fab988cbc088e28fe0. Cụ thể, hoặc thay đổi để sử dụng b'\r\n' hoặc (entry + endline).encode("utf-8") sẽ kích hoạt nó, tuy nhiên cả hai thứ này không nằm trong đường dẫn thực thi cho pip install -vvv INITools là lệnh mà nó không thể thực thi.

Khi cố gắng theo dõi sự cố, tôi nhận thấy rằng nếu tôi thay thế ít nhất một cuộc gọi thành "something".encode("utf8") với (lambda: "something")().encode("utf8") hoạt động.

Một vấn đề khác trong khi cố gắng gỡ lỗi này, đã có nhiều điều tôi đã thử (thêm câu lệnh in, chức năng no-op atexit, sử dụng trollious cho async subprocess) sẽ chuyển vấn đề từ một trường hợp kiểm tra cụ thể trên phiên bản Python cụ thể cho các trường hợp thử nghiệm khác nhau trên các phiên bản Python khác nhau.

Tôi biết thực tế rằng mô-đun subprocess có thể bế tắc nếu bạn đọc/ghi trực tiếp từ subprocess.Popen().stdout/stderr/stdin. Tuy nhiên Mã này đang sử dụng phương thức communicate() được cho là giải quyết các vấn đề này. Đó là bên trong của cuộc gọi wait() rằng communicate() hiện rằng quá trình treo vĩnh viễn chờ đợi cho quá trình pip để thoát.

thông tin khác:

  • Nó là rất heisenbug-ey, tôi đã quản lý để làm cho nó biến mất hoặc Shift dựa trên những thứ khác nhau mà không cần phải có bất kỳ ảnh hưởng đến nó.
  • Tôi đã lần lượt thực hiện bên trong pip chính từ đầu đến cuối đường dẫn mã cho đến khi sys.exit() được gọi.
  • Thay thế sys.exit() bằng os._exit() khắc phục tất cả sự cố treo, tuy nhiên tôi không muốn làm điều đó vì chúng tôi sẽ bỏ qua dọn dẹp mà trình thông dịch Python thực hiện.
  • Không có chủ đề bổ sung nào đang chạy (được xác minh với threading.enumerate).
  • Tôi đã có một số kết hợp thay đổi đã bị treo ngay cả khi không sử dụng subprocess.PIPE cho stdout/stderr/stdin, tuy nhiên các kết hợp khác sẽ có số không nếu không được sử dụng (hoặc nó sẽ chuyển sang một trường hợp thử nghiệm/phiên bản python khác).
  • Dường như không liên quan đến thời gian, bất kỳ cam kết cụ thể nào sẽ thất bại 100% thời gian đối với trường hợp kiểm tra ảnh hưởng/Pythons hoặc không đạt 0% thời gian.
  • Thông thường, mã đã bị thay đổi thậm chí không được thực hiện bởi đường dẫn mã cụ thể đó trong quy trình con pip, tuy nhiên sự tồn tại của thay đổi dường như phá vỡ nó.
  • Tôi đã thử vô hiệu hóa thế hệ bytecode bằng cách sử dụng và có hiệu ứng trong một kết hợp, nhưng ở những người khác nó không có hiệu lực.
  • Lệnh mà các cuộc gọi con gọi là không phải treo trong mọi lệnh gọi (các lệnh tương tự được phát hành thông qua bộ thử nghiệm), tuy nhiên nó luôn nằm ở cùng một vị trí cho một cam kết cụ thể.
  • Cho đến nay tôi đã hoàn toàn không thể tái tạo điều này bên ngoài được gọi qua subproccess trong bộ thử nghiệm, tuy nhiên tôi không biết thực tế nếu nó có hoặc không liên quan đến điều đó.

Tôi hoàn toàn thua lỗ vì những gì có thể gây ra điều này.

CẬP NHẬT # 1

Sử dụng faulthandler.dump_traceback_later() tôi có kết quả này:

Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/requests/packages/urllib3/response.py", line 287 in closed 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/requests/packages/urllib3/response.py", line 287 in closed 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
Timeout (0:00:05)! 
Current thread 0x00007f417bd92740: 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/requests/packages/urllib3/response.py", line 285 in closed 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 
    [ Duplicate Lines Snipped ] 
    File "/tmp/pytest-10/test_env_vars_override_config_file0/pip_src/pip/_vendor/cachecontrol/filewrapper.py", line 24 in __getattr__ 

này gợi ý với tôi rằng có lẽ vấn đề là một cái gì đó để làm với thu gom rác thải và urllib3? Các Filewrapper trong pip._vendor.cachecontrol.filewrapper được sử dụng như một wrapper xung quanh một đối tượng phản ứng urllib3 (phân lớp io.IOBase) để chúng tôi có thể tee phương pháp read() để lưu trữ kết quả của mỗi cuộc gọi đọc trong bộ đệm cũng như trả lại, và sau đó một khi tệp được tiêu thụ hoàn toàn chạy một cuộc gọi lại với nội dung của bộ đệm đó để chúng tôi có thể lưu trữ các mục trong bộ nhớ cache. Điều này có thể tương tác với GC theo một cách nào đó không?

Update # 2

Nếu tôi thêm một phương pháp def __del__(self): pass đến lớp Filewrapper, sau đó tất cả mọi thứ hoạt động chính xác trong các trường hợp tôi đã cố gắng. Tôi đã thử nghiệm để đảm bảo rằng điều này không phải vì tôi vừa tình cờ xác định một phương pháp (đôi khi "sửa lỗi") bằng cách thay đổi điều đó thành def __del2__(self): pass và nó lại bắt đầu thất bại.Tôi không chắc chắn lý do tại sao hoạt động chính xác và phương pháp không có phương thức __del__ có vẻ như nó ít hơn tối ưu.

Update # 3

Thêm một công cụ in import gc; gc.set_debug(gc.DEBUG_UNCOLLECTABLE) để thiết bị lỗi chuẩn hai lần trong việc thực hiện các lệnh pip đã được treo, đó là:

gc: uncollectable <CallbackFileWrapper 0x7f66385c1cd0> 
gc: uncollectable <dict 0x7f663821d5a8> 
gc: uncollectable <functools.partial 0x7f663831de10> 
gc: uncollectable <_io.BytesIO 0x7f663804dd50> 
gc: uncollectable <method 0x7f6638219170> 
gc: uncollectable <tuple 0x7f663852bd40> 
gc: uncollectable <HTTPResponse 0x7f663831c7d0> 
gc: uncollectable <PreparedRequest 0x7f66385c1a90> 
gc: uncollectable <dict 0x7f663852cb48> 
gc: uncollectable <dict 0x7f6637fdcab8> 
gc: uncollectable <HTTPHeaderDict 0x7f663831cb90> 
gc: uncollectable <CaseInsensitiveDict 0x7f66385c1ad0> 
gc: uncollectable <dict 0x7f6638218ab8> 
gc: uncollectable <RequestsCookieJar 0x7f663805d7d0> 
gc: uncollectable <dict 0x7f66382140e0> 
gc: uncollectable <dict 0x7f6638218680> 
gc: uncollectable <list 0x7f6638218e18> 
gc: uncollectable <dict 0x7f6637f14878> 
gc: uncollectable <dict 0x7f663852c5a8> 
gc: uncollectable <dict 0x7f663852cb00> 
gc: uncollectable <method 0x7f6638219d88> 
gc: uncollectable <DefaultCookiePolicy 0x7f663805d590> 
gc: uncollectable <list 0x7f6637f14518> 
gc: uncollectable <list 0x7f6637f285a8> 
gc: uncollectable <list 0x7f6637f144d0> 
gc: uncollectable <list 0x7f6637f14ab8> 
gc: uncollectable <list 0x7f6637f28098> 
gc: uncollectable <list 0x7f6637f14c20> 
gc: uncollectable <list 0x7f6637f145a8> 
gc: uncollectable <list 0x7f6637f14440> 
gc: uncollectable <list 0x7f663852c560> 
gc: uncollectable <list 0x7f6637f26170> 
gc: uncollectable <list 0x7f663821e4d0> 
gc: uncollectable <list 0x7f6637f2d050> 
gc: uncollectable <list 0x7f6637f14fc8> 
gc: uncollectable <list 0x7f6637f142d8> 
gc: uncollectable <list 0x7f663821d050> 
gc: uncollectable <list 0x7f6637f14128> 
gc: uncollectable <tuple 0x7f6637fa8d40> 
gc: uncollectable <tuple 0x7f66382189e0> 
gc: uncollectable <tuple 0x7f66382183f8> 
gc: uncollectable <tuple 0x7f663866cc68> 
gc: uncollectable <tuple 0x7f6637f1e710> 
gc: uncollectable <tuple 0x7f6637fc77a0> 
gc: uncollectable <tuple 0x7f6637f289e0> 
gc: uncollectable <tuple 0x7f6637f19f80> 
gc: uncollectable <tuple 0x7f6638534d40> 
gc: uncollectable <tuple 0x7f6637f259e0> 
gc: uncollectable <tuple 0x7f6637f1c7a0> 
gc: uncollectable <tuple 0x7f6637fc8c20> 
gc: uncollectable <tuple 0x7f6638603878> 
gc: uncollectable <tuple 0x7f6637f23440> 
gc: uncollectable <tuple 0x7f663852c248> 
gc: uncollectable <tuple 0x7f6637f2a0e0> 
gc: uncollectable <tuple 0x7f66386a6ea8> 
gc: uncollectable <tuple 0x7f663852f9e0> 
gc: uncollectable <tuple 0x7f6637f28560> 

và sau đó

gc: uncollectable <CallbackFileWrapper 0x7f66385c1350> 
gc: uncollectable <dict 0x7f6638c33320> 
gc: uncollectable <HTTPResponse 0x7f66385c1590> 
gc: uncollectable <functools.partial 0x7f6637f03ec0> 
gc: uncollectable <_io.BytesIO 0x7f663804d600> 
gc: uncollectable <dict 0x7f6637f1f680> 
gc: uncollectable <method 0x7f663902d3b0> 
gc: uncollectable <tuple 0x7f663852be18> 
gc: uncollectable <HTTPMessage 0x7f66385c1c10> 
gc: uncollectable <HTTPResponse 0x7f66385c1450> 
gc: uncollectable <PreparedRequest 0x7f66385cac50> 
gc: uncollectable <dict 0x7f6637f2f248> 
gc: uncollectable <dict 0x7f6637f28b90> 
gc: uncollectable <dict 0x7f6637f1e638> 
gc: uncollectable <list 0x7f6637f26cb0> 
gc: uncollectable <list 0x7f6637f2f638> 
gc: uncollectable <HTTPHeaderDict 0x7f66385c1f90> 
gc: uncollectable <CaseInsensitiveDict 0x7f66385b2890> 
gc: uncollectable <dict 0x7f6638bd9200> 
gc: uncollectable <RequestsCookieJar 0x7f663805da50> 
gc: uncollectable <dict 0x7f6637f28a28> 
gc: uncollectable <dict 0x7f663853aa28> 
gc: uncollectable <list 0x7f663853a6c8> 
gc: uncollectable <dict 0x7f6638ede5f0> 
gc: uncollectable <dict 0x7f6637f285f0> 
gc: uncollectable <dict 0x7f663853a4d0> 
gc: uncollectable <method 0x7f663911f710> 
gc: uncollectable <DefaultCookiePolicy 0x7f663805d210> 
gc: uncollectable <list 0x7f6637f28ab8> 
gc: uncollectable <list 0x7f6638215050> 
gc: uncollectable <list 0x7f663853a200> 
gc: uncollectable <list 0x7f6638215a28> 
gc: uncollectable <list 0x7f663853a950> 
gc: uncollectable <list 0x7f663853a998> 
gc: uncollectable <list 0x7f6637f21638> 
gc: uncollectable <list 0x7f6637f0cd40> 
gc: uncollectable <list 0x7f663853ac68> 
gc: uncollectable <list 0x7f6637f22c68> 
gc: uncollectable <list 0x7f663853a170> 
gc: uncollectable <list 0x7f6637fa6a28> 
gc: uncollectable <list 0x7f66382153b0> 
gc: uncollectable <list 0x7f66386a5e60> 
gc: uncollectable <list 0x7f663852f2d8> 
gc: uncollectable <list 0x7f66386a3320> 
    [<pip._vendor.cachecontrol.filewrapper.CallbackFileWrapper object at 0x7f66385c1cd0>, <pip._vendor.cachecontrol.filewrapper.CallbackFileWrapper object at 0x7f66385c1350>] 

Đó có phải là thông tin hữu ích không? Tôi chưa bao giờ sử dụng lá cờ đó trước đây nên tôi không biết liệu điều đó có khác thường hay không.

+0

Chà, bạn không đùa khi bạn nói điều đó thật kỳ quặc ... làm rõ ràng các luồng đầu ra trước khi sys.exit() gọi tạo sự khác biệt? – ncoghlan

+0

faulthandler.dump_traceback_later() cũng có thể hữu ích. Nó không có trong stdlib trên 3.2, nhưng phiên bản PyPI sẽ hoạt động: https://pypi.python.org/pypi/faulthandler – ncoghlan

+0

Tôi đã quét nhanh qua danh sách các lỗi "xử lý con", nhưng không thấy bất kỳ trông giống như thủ phạm rõ ràng: http://bugs.python.org/issue?%40columns=id%2Cactivity%2Ctitle%2Ccreator%2Cassignee%2Cstatus%2Ctype&%40sort=-activity&%40filter=status&%40action=searchid&ignore=file% 3Acontent &% 40search_text = subprocess + hang & submit = tìm kiếm & trạng thái = -1% 2C1% 2C2% 2C3 – ncoghlan

Trả lời

19

Trong Python 2, nếu một tập hợp các đối tượng được liên kết với nhau trong một chuỗi (chu trình tham chiếu) và, ít nhất, một đối tượng có phương thức __del__, trình thu gom rác sẽ không xóa các đối tượng này. Nếu bạn có chu kỳ tham chiếu, việc thêm phương thức __del__() có thể chỉ ẩn các lỗi (lỗi giải pháp).

Theo bản cập nhật # 3 của bạn, có vẻ như bạn có vấn đề như vậy.

+0

Cách tốt nhất để định vị và sửa lỗi này là gì? Tôi đã không bao giờ thực sự phải đối phó với điều này trước khi vì vậy nó là một cái mới cho tôi :) –

+0

Với PR bạn đã đề cập trong nhận xét, có vẻ như bạn đã tìm thấy chu kỳ vi phạm và đã có thể phá vỡ nó một cách rõ ràng để tránh dựa vào cyclic GC (và do đó có khả năng xóa mọi thứ vào thời điểm xấu trong khi tắt máy). Tôi không biết về bất kỳ tùy chọn nào tốt hơn là lướt qua các số liệu thống kê gỡ lỗi GC như bạn đã làm. – ncoghlan

+1

Vấn đề không phải là thiếu bộ sưu tập mà thực tế là nó bị treo. Tôi cho rằng những gì đang xảy ra là nó chạy vào các vấn đề về việc tắt trình thông dịch. –

Các vấn đề liên quan