2013-07-04 2 views
0

Я написал базовую программу для проверки дерева каталогов, содержащего много файлов jpeg (500000+) , убедитесь, что они не повреждены (примерно 3-5% файлов выглядят чтобы быть поврежденным в некотором роде), а затем взять sha1sum файлов (даже поврежденных) и сохранить информацию в базе данных.Python IOError не может выделить память, хотя есть много

Файлы jpeg, о которых идет речь, находятся в системе Windows и устанавливаются в окне linux через cifs. Они в основном размером около 4 мегабайт, хотя некоторые могут быть немного больше или меньше.

Когда я запускаю программу, она работает довольно хорошо некоторое время, а затем она падает с ошибкой ниже. Это было после обработки около 1100 файлов (ошибка показала, что проблема возникла при попытке открыть файл размером 4,5 мегабайта).

Теперь я понимаю, что могу уловить эту ошибку и продолжить или повторить попытку, но мне любопытно, почему это происходит в первую очередь, и если ловить и повторить попытку на самом деле собирается решить проблему - или это будет просто застрял в повторной попытке (если я не ограничу повторные попытки, но затем файл пропущен).

Я использую «Python 2.7.5+» в системе debian для ее запуска. Система имеет как минимум 4 Gig (возможно, 8) бара, а верхняя часть сообщает, что скрипт использует менее 1% бара и менее 3% процессора в любое время, когда он работает. Аналогично, jpeginfo, который запускает этот скрипт, также использует одинаково небольшие объемы памяти и процессора.

Чтобы избежать использования слишком большого объема памяти при чтении файлов я принял подход, приведенные в этом ответе на другой вопрос: https://stackoverflow.com/a/1131255/289545

Также вы можете отметить, что команда «jpeginfo» находится в цикле, ища «[OK]» ответ. Это потому, что если «jpeginfo» считает, что он не может найти файл, он возвращает 0, и поэтому он не считается состоянием ошибки вызовом subprocess.check_output.

Я действительно задавался вопросом, может ли связать jpeginfo с некоторыми файлами с первой попытки (и я подозреваю, что это так), но возвращенная ошибка говорит, что не может выделить память, а не файл не найден.

Ошибка:

Traceback (most recent call last): 
    File "/home/m3z/jpeg_tester", line 95, in <module> 
    main() 
    File "/home/m3z/jpeg_tester", line 32, in __init__ 
    self.recurse(self.args.dir, self.scan) 
    File "/home/m3z/jpeg_tester", line 87, in recurse 
    cmd(os.path.join(root, name)) 
    File "/home/m3z/jpeg_tester", line 69, in scan 
    with open(filepath) as f: 
IOError: [Errno 12] Cannot allocate memory: '/path/to/file name.jpg' 

Полный код программы:

1 #!/usr/bin/env python 
    2 
    3 import os 
    4 import time 
    5 import subprocess 
    6 import argparse 
    7 import hashlib 
    8 import oursql as sql 
    9 
10 
11 
12 class main: 
13  def __init__(self): 
14   parser = argparse.ArgumentParser(description='Check jpeg files in a given directory for errors') 
15   parser.add_argument('dir',action='store', help="absolute path to the directory to check") 
16   parser.add_argument('-r, --recurse', dest="recurse", action='store_true', help="should we check subdirectories") 
17   parser.add_argument('-s, --scan', dest="scan", action='store_true', help="initiate scan?") 
18   parser.add_argument('-i, --index', dest="index", action='store_true', help="should we index the files?") 
19 
20   self.args = parser.parse_args() 
21   self.results = [] 
22 
23   if not self.args.dir.startswith("/"): 
24     print "dir must be absolute" 
25     quit() 
26 
27   if self.args.index: 
28     self.db = sql.connect(host="localhost",user="...",passwd="...",db="fileindex") 
29     self.cursor = self.db.cursor() 
30 
31   if self.args.recurse: 
32     self.recurse(self.args.dir, self.scan) 
33   else: 
34     self.scan(self.args.dir) 
35 
36   if self.db: 
37     self.db.close() 
38 
39   for line in self.results: 
40     print line 
41 
42 
43 
44  def scan(self, dirpath): 
45   print "Scanning %s" % (dirpath) 
46   filelist = os.listdir(dirpath) 
47   filelist.sort() 
48   total = len(filelist) 
49   index = 0 
50   for filen in filelist: 
51     if filen.lower().endswith(".jpg") or filen.lower().endswith(".jpeg"): 
52       filepath = os.path.join(dirpath, filen) 
53       index = index+1 
54       if self.args.scan: 
55         try: 
56           procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip() 
57           while "[OK]" not in procresult: 
58             time.sleep(0.5) 
59             print "\tRetrying %s" % (filepath) 
60             procresult = subprocess.check_output(['jpeginfo','-c',filepath]).strip() 
61           print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,procresult) 
62         except subprocess.CalledProcessError, e: 
63           os.renames(filepath, os.path.join(dirpath, "dodgy",filen)) 
64           filepath = os.path.join(dirpath, "dodgy", filen) 
65           self.results.append("Trouble with: %s" % (filepath)) 
66           print "%s/%s: %s" % ('{:>5}'.format(str(index)),total,e.output.strip()) 
67       if self.args.index: 
68         sha1 = hashlib.sha1() 
69         with open(filepath) as f: 
70           while True: 
71             data = f.read(8192) 
72             if not data: 
73               break 
74             sha1.update(data) 
75         sqlcmd = ("INSERT INTO `index` (`sha1`,`path`,`filename`) VALUES (?, ?, ?);", (buffer(sha1.digest()), dirpath, filen)) 
76         self.cursor.execute(*sqlcmd) 
77 
78 
79  def recurse(self, dirpath, cmd, on_files=False): 
80   for root, dirs, files in os.walk(dirpath): 
81    if on_files: 
82     for name in files: 
83      cmd(os.path.join(root, name)) 
84    else: 
85     cmd(root) 
86     for name in dirs: 
87      cmd(os.path.join(root, name)) 
88 
89 
90 
91 
92 
93 
94 if __name__ == "__main__": 
95  main() 
+0

Ваша программа по-прежнему имеет много памяти, но она, возможно, исчерпала другие ресурсы. Может быть, дескрипторы файлов? Вы все еще получаете исключение, если вы закомментируете вызовы подпроцесса? –

+0

Вам не нужно закрывать файл для 'with open (filepath) как f' с помощью' f.close() '? Простите меня, поскольку я новичок в python. – shahkalpesh

+2

@shahkalpesh: no, 'with' позаботится об этом, как только вы покинете блок. – RickyA

ответ

2

Он смотрит на меня, как Python просто проходя по ошибке от основной open() вызова и реальным виновником здесь является Поддержка Linux CIFS - я сомневаюсь, что Python будет синтезировать ENOMEM, если системная память не будет действительно исчерпана (и, вероятно, даже тогда я ожидал, что Linux OOM killer будет вызван вместо получения ENOMEM).

К сожалению, для выяснения того, что там происходит, может понадобиться что-то вроде специалиста по файловой системе Linux, но, глядя на sources for CIFS in the Linux kernel, я вижу множество мест, где ENOMEM возвращается, когда различные ресурсы, зависящие от ядра, исчерпаны, в отличие от общая системная память, но я недостаточно знаком с этим, чтобы сказать, насколько вероятно любой из них.

Чтобы исключить что-либо специфичное для Python, вы можете запустить процесс под strace, чтобы вы могли видеть точный код возврата, который Python получает из Linux.Для этого запустите команду что-то вроде этого:

strace -eopen -f python myscript.py myarg1 myarg2 2>strace.log 

-f будет следовать дочерние процессы (т.е. jpeginfo команд, которые вы запускаете) и -eopen только покажет вам open() вызовы в отличие от всех системных вызовов (которые это то, что strace делает по умолчанию). Это может привести к разумному объему вывода, поэтому я перенаправил его в файл в приведенном выше примере, но вы можете оставить его на своем терминале, если хотите.

Я ожидаю, что вы бы увидеть что-то вроде этого как раз перед вами получить исключение:

open("/path/to/file name.jpg", O_RDONLY) = -1 ENOMEM (Cannot allocate memory) 

Если да, то эта ошибка приходит прямо из файловой системы open() вызова и есть очень мало вы можете с этим поделать в вашем скрипте Python. Вы можете поймать исключение и повторить попытку (возможно, после небольшой задержки), как вы уже делаете, если jpeginfo терпит неудачу, но трудно сказать, насколько успешной эта стратегия будет, не зная, что вызывает ошибки в первую очередь.

Вы могли бы, конечно, скопировать файлы локально, но похоже, что это будет серьезная боль, так как их так много.

EDIT: Как и в сторону, вы будете ожидать, чтобы увидеть много open() вызовов, которые не имеют ничего общего с вашим сценарием, потому что strace прослеживает каждый прозвучавший на Python, который включает в себя это открытие его собственного .py и .pyc файлы, например. Просто игнорируйте те, которые не относятся к файлам, которые вас интересуют.

+0

Ничего себе, спасибо за подробный ответ. Я дам вам поглядеть. – m3z

+0

Я сделал, как вы предлагаете, но файл журнала strace массивный, и у меня еще не было возможности пройти его. Я также переписал свою программу, чтобы повторить несколько раз с задержкой. Кажется, что проблемы решены после половины второй задержки. Спасибо – m3z

+0

Посмотрев на strace, это была файловая система, возвращающая ENOMEM, как вы и предполагали. Спасибо – m3z

Смежные вопросы