質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

AWS(Amazon Web Services)

Amazon Web Services (AWS)は、仮想空間を機軸とした、クラスター状のコンピュータ・ネットワーク・データベース・ストーレッジ・サポートツールをAWSというインフラから提供する商用サービスです。

Q&A

解決済

3回答

4048閲覧

boto3のマルチパート転送時のハンドルリーク

TomeSq

総合スコア10

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

AWS(Amazon Web Services)

Amazon Web Services (AWS)は、仮想空間を機軸とした、クラスター状のコンピュータ・ネットワーク・データベース・ストーレッジ・サポートツールをAWSというインフラから提供する商用サービスです。

0グッド

0クリップ

投稿2018/11/05 07:01

編集2018/11/06 01:22

質問

boto3を使用してプログラムを常駐させて、s3へのファイルアップロードを行っているのですが、gcでもハンドルが回収されず右肩上りになっています。
以下の設定をupload_fileの引数に指定するでリーク自体は収まるのでマルチパート転送時固有の問題みたいです。

python

1TransferConfig(use_threads=False)

そこで質問です。
・pythonでハンドルリークを検出する方法はあるのでしょうか
・boto3でオブジェクトを開放するよう外部から呼び出せる関数はあるのでしょうか

環境

・windows10
・python 3.6(64bit)
・boto3 1.9.23
・botocore 1.12.23

根拠

Windowsのパフォーマンスカウンターで\Process(python)\Handle Countで見ております。

検証コード

python

1class s3Session: 2 def __init__(self, access_key, secret_access_key, region_name=None): 3 self._session = Session(aws_access_key_id=access_key, 4 aws_secret_access_key=secret_access_key, 5 region_name=region_name) 6 7 def __enter__(self): 8 return self 9 10 def __exit__(self, type, value, traceback): 11 return True 12 13 @property 14 def client(self): 15 return self._session.resource('s3').meta.client 16 17def s3_upload_loop(s3Session, writeByte, bucket): 18 while True: 19 try: 20 # ダミーのファイル出力 21 with tempfile.NamedTemporaryFile(delete=False) as tempFile: 22 tempFile.write(numpy.random.bytes(int(writeByte))) 23 tempName = tempFile.name 24 25 s3Session.client.upload_file(tempName, bucket, os.path.basename(tempName)) 26 gc.collect() 27 except botocore.exceptions.EndpointConnectionError: 28 print("Network Error: Please Check your Internet Connection") 29 except Exception as e: 30 print(str(e)) 31 finally: 32 os.remove(tempName) 33 34 35if __name__ == '__main__': 36 bucket = 'バケット名' 37 38 with s3Session('アクセスキー', 'シークレットキー') as sesstion : 39 s3_upload_loop(sesstion, 10*1024*1024, bucket)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TomeSq

2018/11/06 00:35 編集

ProcessExplorerで同じハンドル数にしかならなかったので、同じsysinternalsのHandleツールで確認するとどうもSemaphoreハンドルの開放が漏れているようです。1ループ目:143、2ループ目:148となっていました。
guest

回答3

0

自己解決

転送する際、以下のようにTransferManagerを経由でS3Transferを作成して使いまわして上げればリークしないことがわかりました。

python

1def s3_upload_loop(access_key, secret_access_key, writeByte, bucket): 2 3 config = TransferConfig(use_threads = True) 4 client = boto3.session.Session(access_key, secret_access_key).resource('s3').meta.client 5 with TransferManager(client, config) as manager : 6 with S3Transfer(manager = manager) as transfer: 7 while True: 8 try: 9 # ダミーのファイル出力 10 with tempfile.NamedTemporaryFile(delete=False) as tempFile: 11 tempFile.write(numpy.random.bytes(int(writeByte))) 12 tempName = tempFile.name 13 14 transfer.upload_file(tempName, bucket, os.path.basename(tempName)) 15 16 gc.collect() 17 except botocore.exceptions.EndpointConnectionError: 18 print("Network Error: Please Check your Internet Connection") 19 except Exception as e: 20 print(str(e)) 21 finally: 22 os.remove(tempName)

違いを調べたところupload_file内でThreadPoolExecutorを毎回作成しているのが原因でした。TransferManager経由だと作成済みのThreadPoolExecutorを使いまわしているのでリークしないようです。

単純に以下の処理だけでもリークするのでThreadPoolExecutorの終了処理?がうまく行っていないように見受けられます。

python

1def task(): 2 pass 3 4if __name__ == '__main__': 5 while True: 6 with ThreadPoolExecutor(max_workers=2, thread_name_prefix="thread") as executor: 7 executor.submit(task)

ただ、使い方がわるいのか、そもそもThreadPoolExecutorの問題なのかわかりませんがとりあえず原因と回避方法はわかりました。

投稿2018/11/11 23:11

TomeSq

総合スコア10

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

以下にPythonでプロセス情報を取得する必要がありますので、
これを実装してカウントすれば良いと思います。

http://www.denzow.me/entry/2017/09/13/235856

投稿2018/11/09 05:33

moonphase

総合スコア6621

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

以下のコードで再現すればGitHubのIssueへレポートするのがいいんじゃないでしょうか。

python

1from io import BytesIO 2from os import environ 3from boto3 import Session 4 5 6creds = dict(aws_access_key_id=environ.get("AWS_ACCESS_KEY_ID"), 7 aws_secret_access_key=environ.get("AWS_SECRET_ACCESS_KEY"), 8 region_name=environ.get("AWS_DEFAULT_REGION ")) 9 10 11with Session(**creds) as sess, BytesIO("DUMMY") as fp: 12 client = sess.resource("s3").meta.client 13 for i in range(10): 14 fp.seek(0) 15 client.upload_fileobj(fp, "my-test-bucket-2018-11-06", "obj-%d" % i)

ちなみにWindows環境でハンドルまわりをPythonで扱うためのコードは以下みたいになります。ご興味があれば。

python

1''' 2Created on 2017/11/07 3 4@author: sakurai 5''' 6from ctypes import byref 7from ctypes import cast 8from ctypes import create_string_buffer 9from ctypes import create_unicode_buffer 10from ctypes import POINTER 11from ctypes import Structure 12from ctypes import windll 13from ctypes import WinDLL 14from ctypes import WinError 15from ctypes import wintypes 16from ctypes.wintypes import LPVOID as PVOID 17from os.path import isfile 18 19 20NTDLL = WinDLL("ntdll") 21KERNEL32 = windll.kernel32 22NTSTATUS = wintypes.LONG 23SYSTEM_INFORMATION_CLASS = wintypes.ULONG 24 25 26#### 27# C structures 28#### 29class SYSTEM_HANDLE(Structure): 30 """https://stackoverflow.com/a/5163277""" 31 _fields_ = (("dwProcessId", wintypes.DWORD), 32 ("bObjectType", wintypes.BYTE), 33 ("bFlags", wintypes.BYTE), 34 ("wValue", wintypes.WORD), 35 ("pAddress", PVOID), 36 ("GrantedAccess", wintypes.DWORD)) 37 38 39class SYSTEM_HANDLE_INFORMATION(Structure): 40 _fields_ = (("dwCount", wintypes.DWORD), 41 ("Handles", SYSTEM_HANDLE * 1)) 42 43 44#### 45# C error checks 46#### 47def assertZero(result, _, args): 48 if result != 0: 49 raise WinError(NTDLL.RtlNtStatusToDosError(result)) 50 return args 51 52 53def assertNotZero(result, _, args): 54 if result == 0: 55 raise WinError() 56 return args 57 58 59def assertTrue(result, _, args): 60 if not result: 61 raise WinError() 62 return args 63 64 65#### 66# C functions 67#### 68NtQuerySystemInformation = NTDLL.NtQuerySystemInformation 69NtQuerySystemInformation.restype = NTSTATUS 70NtQuerySystemInformation.argtypes = (SYSTEM_INFORMATION_CLASS, 71 PVOID, 72 wintypes.ULONG, 73 wintypes.PULONG) 74NtQuerySystemInformation.errcheck = assertZero 75 76GetFinalPathNameByHandle = KERNEL32.GetFinalPathNameByHandleW 77GetFinalPathNameByHandle.restype = wintypes.DWORD 78GetFinalPathNameByHandle.argtypes = (wintypes.HANDLE, 79 wintypes.LPWSTR, 80 wintypes.DWORD, 81 wintypes.DWORD) 82GetFinalPathNameByHandle.errcheck = assertNotZero 83 84CloseHandle = KERNEL32.CloseHandle 85CloseHandle.restype = wintypes.BOOL 86CloseHandle.argtypes = (wintypes.HANDLE, ) 87CloseHandle.errcheck = assertTrue 88 89 90#### 91# Wrappers 92#### 93def system_handles(): 94 dwSize = wintypes.DWORD(0) 95 pInfo = create_string_buffer(dwSize.value) 96 while True: 97 try: 98 NtQuerySystemInformation(0x10, # SystemHandleInformation 99 pInfo, dwSize, byref(dwSize)) 100 break 101 except OSError as e: 102 if e.errno == 13: 103 pInfo = create_string_buffer(dwSize.value) 104 else: 105 raise e 106 107 info = cast(pInfo, POINTER(SYSTEM_HANDLE_INFORMATION)).contents 108 yield from cast(info.Handles, 109 POINTER(SYSTEM_HANDLE * info.dwCount)).contents 110 111 112def get_path(handle: wintypes.HANDLE): 113 szPath = create_unicode_buffer(wintypes.MAX_PATH) 114 GetFinalPathNameByHandle(handle, szPath, wintypes.MAX_PATH, 0) 115 return szPath.value 116 117 118def open_files(pid: int): 119 for handle in system_handles(): 120 if handle.dwProcessId == pid: 121 try: 122 fname = get_path(handle.wValue) 123 if isfile(fname): 124 yield (get_path(handle.wValue), handle.wValue) 125 except OSError: 126 pass

投稿2018/11/06 01:47

YouheiSakurai

総合スコア6142

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問