Как решить утечку памяти Python при использовании urrlib2? - PullRequest
8 голосов
/ 18 ноября 2010

Я пытаюсь написать простой скрипт на Python для моего мобильного телефона, чтобы периодически загружать веб-страницу с помощью urrlib2. На самом деле меня не волнует ответ сервера, я бы хотел передать PHP только некоторые значения в URL. Проблема заключается в том, что Python для S60 использует старое ядро ​​2.5.4 Python, которое, похоже, имеет утечку памяти в модуле urrlib2. Когда я читаю, кажется, что такие проблемы есть и в каждом типе сетевых коммуникаций. Об этой ошибке сообщалось здесь пару лет назад, хотя некоторые обходные пути были также опубликованы. Я перепробовал все, что смог найти на этой странице, и с помощью Google, но мой телефон все еще не хватает памяти после ~ 70 загрузки страницы. Странно, но Garbege Collector, похоже, тоже ничего не меняет, за исключением того, что мой сценарий стал намного медленнее. Говорят, что новое ядро ​​(3.1) решает эту проблему, но, к сожалению, я не могу ждать год (или больше), пока не появится порт S60.

вот как выглядит мой скрипт после добавления каждого небольшого трюка, который я нашел:

<code>
import urrlib2, httplib, gc
while(true):
 url = "http://something.com/foo.php?parameter=" + value 
 f = urllib2.urlopen(url)
 f.read(1)
 f.fp._sock.recv=None # hacky avoidance
 f.close()
 del f
 gc.collect()
Любые предложения, как заставить это работать навсегда, не получая ошибку «не может выделить память»? Спасибо за продвижение, ура, б_м

Обновление: Мне удалось подключиться 92 раза, прежде чем не хватило памяти, но это все еще недостаточно хорошо.

Update2: Пробовал метод сокетов, как предлагалось ранее, это второе лучшее (неправильное) решение на данный момент:

<code>
class UpdateSocketThread(threading.Thread):
  def run(self):
  global data
  while 1:
  url = "/foo.php?parameter=%d"%data
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect(('something.com', 80))
  s.send('GET '+url+' HTTP/1.0\r\n\r\n')
  s.close()
  sleep(1)
Я попробовал маленькие хитрости, тоже сверху. Поток закрывается после ~ 50 загрузок (у телефона осталось 50 МБ памяти, очевидно, что у оболочки Python нет.)

UPDATE : Я думаю, что я все ближе к решению! Я попытался отправить несколько данных без закрытия и повторного открытия сокета. Это может быть ключом, так как этот метод оставит только один дескриптор открытого файла. Проблема:

<code>
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect(("something.com", 80))
socket.send("test") #returns 4 (sent bytes, which is cool)
socket.send("test") #4
socket.send("test") #4
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns the number of sent bytes, ok
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("test") #returns 0, strange...
*: сообщение об ошибке: 10053, программное обеспечение вызвало прерывание соединения

Почему я не могу отправить несколько сообщений ??

Ответы [ 7 ]

1 голос
/ 19 ноября 2010

Используя тестовый код, предложенный по вашей ссылке, я протестировал установку Python и подтвердил, что она действительно утечка. Но если, как предложил @Russell, я поместил каждый urlopen в свой собственный процесс, ОС должна устранить утечки памяти. В моих тестах память, недоступные объекты и открытые файлы остаются более или менее постоянными. Я разделил код на два файла:

connection.py

import cPickle, urllib2

def connectFunction(queryString):
    conn = urllib2.urlopen('http://something.com/foo.php?parameter='+str(queryString))
    data = conn.read()
    outfile = ('sometempfile'. 'wb')
    cPickle.dump(data, outfile)
    outfile.close()

if __name__ == '__main__':
    connectFunction(sys.argv[1])

###launcher.py
import subprocess, cPickle

#code from your link to check the number of unreachable objects

def print_unreachable_len():
    # check memory on memory leaks
    import gc
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []

    for it in gc.garbage:
        unreachableL.append(it)
    return len(str(unreachableL))

    #my code
    if __name__ == '__main__':        
        print 'Before running a single process:', print_unreachable_len()
        return_value_list = []
        for i, value in enumerate(values): #where values is a list or a generator containing (or yielding) the parameters to pass to the URL
             subprocess.call(['python', 'connection.py', str(value)])
             print 'after running', i, 'processes:', print_unreachable_len()
             infile = open('sometempfile', 'rb')
             return_value_list.append(cPickle.load(infile))
             infile.close()

Очевидно, что это последовательно, поэтому вы будете выполнять только одно соединение за раз, что может быть или не быть проблемой для вас. Если это так, вам придется найти неблокирующий способ общения с запускаемыми процессами, но я оставлю это как упражнение для вас.

РЕДАКТИРОВАТЬ : При повторном чтении вашего вопроса, вам кажется, что вас не волнует ответ сервера. В этом случае вы можете избавиться от всего кода, связанного с травлением. И очевидно, что в окончательном коде у вас не будет битов, связанных с print_unreachable_len().

1 голос
/ 22 декабря 2016

В urllib2 существует ссылочный цикл, созданный в urllib2.py:1216.Проблема продолжается и существует с 2009 года. https://bugs.python.org/issue1208304

0 голосов
/ 15 января 2011

Я думаю это , вероятно, ваша проблема.Подводя итог, можно сказать, что в поиске DNS Pys60 произошла утечка памяти, и вы можете обойти ее, переместив поиск DNS за пределы внутреннего цикла.

0 голосов
/ 22 ноября 2010

В зависимости от платформы и версии python, python может не освободить память обратно в ОС. Посмотрите эту нить stackoverflow . Тем не менее, Python не должен бесконечно потреблять память. Судя по используемому вами коду, он кажется ошибкой во время выполнения Python , если , в urllib / sockets используются глобальные переменные, которых я не верю - в этом виноват Python на S60!

Рассматривали ли вы другие источники утечки памяти? Открыт бесконечный лог-файл, постоянно увеличивается массив или что-то в этом роде? Если это действительно ошибка в интерфейсе сокетов, тогда ваш единственный вариант - использовать подпроцессный подход.

0 голосов
/ 21 ноября 2010

Для меня это не утечка с Python 2.6.1 на Mac. Какую версию вы используете?

Кстати, ваша программа не работает из-за нескольких опечаток. Вот тот, который работает:

import urllib2, httplib, gc
value = "foo"
count = 0
while(True):
    url = "http://192.168.1.1/?parameter=" + value 
    f = urllib2.urlopen(url)
    f.read(1)
    f.fp._sock.recv=None # hacky avoidance
    f.close()
    del f
    print "count=",count
    count += 1
0 голосов
/ 19 ноября 2010

Рассмотрите возможность использования низкоуровневого API сокета (связанного howto ) вместо urllib2.

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('GET /path/to/file/index.html HTTP/1.0\n\n')

 # you'll need to figure out how much data to read and read that exactly
 # or wait for read() to return data of zero length (I think!)
DATA_SZ = 1024
data    = s.recv(DATA_SZ)
s.close()
print 'Received', repr(data)

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

edit Здесь может быть полезен ответ об использовании makefile: Базовая аутентификация HTTP с использованием сокетов в python

0 голосов
/ 18 ноября 2010

Это выглядит как (очень!) Хакерский обходной путь, но найден небольшой поиск в Google этот комментарий по проблеме:

Видимо, добавление f.read(1) остановит утечку!

import urllib2
f = urllib2.urlopen('http://www.google.com')
f.read(1)
f.close()

РЕДАКТИРОВАТЬ : о, я вижу, у вас уже есть f.read(1) ... У меня тогда все идеи: /

...