powered by simpleCommunicator - 2.0.60     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / PHP, Perl, Python [игнор отключен] [закрыт для гостей] / [perl] Каким образом освободить память?
10 сообщений из 10, страница 1 из 1
[perl] Каким образом освободить память?
    #38692092
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Доброго дня.

Хочу немного допилить долгоиграющий скрипт. Проблема в том, что при длительной работе отжирание памяти растет и растет. Причина, как мне кажется, в том, что ставшие ненужными ресурсы не освобождаются. Сам скрипт представляет собой FTP-прокси. Будучи запущенным, он принимает подключения клиентов по 21 порту (канал управления), заменяет данные для подключения (подставляет реальный пароль), логгирует действия клиентов и т.п.
Скрипт был писан давно и не мною. Работает - и ладно, но при активной работе клиентов процесс пухнет до несколько сотен мегабайт.

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

Код: php
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
use Socket;
use DBI;
use threads;

#=============== Создаём серверный сокет и начинаем его прослушивать ============
# Преобразуем имя хоста в 32-битный формат
$host = inet_aton($host);
# Преобразуем хост и порт в сруктуру, которую понимает socket()
$sock_name = sockaddr_in($port,$host)
    or die "Couldn't convert $host into an Internet address: $!\n";
# Создаём сокет
socket(SERVER,PF_INET,SOCK_STREAM,getprotobyname('tcp'))
    or die "Couldn't create socket: $!\n";
# Задаём параметры сокета
setsockopt(SERVER,SOL_SOCKET,SO_REUSEADDR,1)
    or die "setsockopt() failed: $!\n";
# Привязываем сокет к реальному IP
bind(SERVER,$sock_name)
    or die "Couldn't bind to port $port: $!\n";
# Начинаем слушать сокет
listen(SERVER,$max_connections);
# Создаём массив нитей
@threads=();
# Создаём массив сокетов
@CLIENTS=();

#============== В бесконечном цикле принимаем подключения клиентов =============
$count=0;
while (1){
    # Счётчик нитей
    $count++;
    # Принимаем подключение по сокету ftp-клиента
    $rem_addr = accept($CLIENTS[$count],SERVER);
...........
    # Создаём новую нить для обработки соединения
    if($client_ip ne '') {$threads[$count]=threads->new(\&client, $count, $CLIENTS[$count]);}
    # Закрываем сокет в основной программе
    close($CLIENTS[$count]);
    sleep 1;
}
# Закрываем серверный сокет
close SERVER;
# ============ Конец основной программы =============

#==================== Подпрограмма - нить, выполняющая функцию FTP-прокси ====================
sub client {
    # Получаем индекс нити в массиве
    my $tid_count=shift;
    # Получаем клиентский сокет
    my $CLIENT=shift;
    # Алло, FTP-прокси слушает(посылаем FTP-клиенту сообщение о готовности)
    send($CLIENT,"220 FTP server ready.\r\n", 0);

#.............
            # Соединяемся с БД
            $dbh = DBI->connect("DBI:mysql:$db_name:$db_host",
#.............
# тут еще много чего делается, используются кое-какие локальные и глобальные переменные... 
#.............

# далее в этой подпрограмме в нескольких местах по каким-то условиям 
# и в конце подпрограммы
# делается выход такого вида:

    # Закрываем сокет клиента
    close($CLIENT);
    # Закрываем соединение с удалённым FTP-сервером
    close (SOCK);
    # Закрываем соединение с БД
    if($dbh){$dbh->disconnect;}
    return 0;
}
# end of sub client



Если правильно понимаю, нужно каким-то образом очистить память после выхода из экземпляра из уже ненужного инстанса sub client, который стартовал в строке:
Код: php
1.
2.
    # Создаём новую нить для обработки соединения
    if($client_ip ne '') {$threads[$count]=threads->new(\&client, $count, $CLIENTS[$count]);}


Но где и как это сделать правильно?

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

Сам я не сталкивался никогда с тредами. Да и оптимизацией перловых скриптов не занимался. Потому прошу наставить на путь истинный. Заранее благодарю.

PS: По крону перестартовать скрипт - это, конечно, тоже решение. Но не слишком удачное, на мой взгляд. :-)
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38692207
Goror
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vkle,

Что может быть надёжней, старого, доброго "RESET:)", если что-то непомерно начинает жрать память и CP, и процесс начинает тормозить работу всей системы, самое надёжное прервать (перезапустить процесс).

Возможно скрипт так и написан, что со временем начинает перегружать память.. Может косяк.. а может по другому никак..
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38692236
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Goror,

Ну так вот уж несколько лет и делаем - как заглючило - так убиваем процесс. Сейчас сервис на другую машину перенес. Костыли поубирать хочу. Или проще написать еще один сервис, который мониторит этот скрип на предмет обжорства и принимает убойные меры?

Должен же быть какой-то unset или что-то вроде... Не?
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38692336
White Owl
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vkleЕсли правильно понимаю, нужно каким-то образом очистить память после выхода из экземпляра из уже ненужного инстанса sub client, который стартовал в строке:
Код: php
1.
2.
    # Создаём новую нить для обработки соединения
    if($client_ip ne '') {$threads[$count]=threads->new(\&client, $count, $CLIENTS[$count]);}


Но где и как это сделать правильно? Ну в общем, да. Поубивать все что создано внутри нити и в конце вызвать threads->exit(). Это надо делать конечно же внутри того самого "ненужного инстанса", а не в главном потоке.
Этого должно хватать.

vkleЕще один момент для некоторой оптимизации, как мне кажется, это освобождение неиспользуемых переменных. Подскажите, какими принципами следует руководствоваться для такой вот структуры?Ну так и делать. Все что появляется на куче изнутри нити, должно нитью же и освобождаться.
Еще можно повторно использовать освободившиеся нити. Если клиент ушел - нить помечается как простаивающая и следующий клиент отдается в уже существующую. Но это надо будет переделывать твой проект.

vklePS: По крону перестартовать скрипт - это, конечно, тоже решение. Но не слишком удачное, на мой взгляд. :-)Ну если уж совсем приперло, то...
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38692396
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
White OwlvkleЕсли правильно понимаю, нужно каким-то образом очистить память после выхода из экземпляра из уже ненужного инстанса sub client, который стартовал в строке:
Код: php
1.
2.
    # Создаём новую нить для обработки соединения
    if($client_ip ne '') {$threads[$count]=threads->new(\&client, $count, $CLIENTS[$count]);}



Но где и как это сделать правильно? Ну в общем, да. Поубивать все что создано внутри нити и в конце вызвать threads->exit(). Это надо делать конечно же внутри того самого "ненужного инстанса", а не в главном потоке.
Этого должно хватать.
Спасибо за ответ. Пытаюсь сперва понять суть и осознать принципы. Потому появились вопросы для уточнения понимания.

В текущем варианте выход из sub client сделан через return 0 . В таком случае после выхода по return 0 в пеерменной $threads[$count] остается ставший ненужным экземпляр "клиента". Но ведь и после threads->exit() в $threads останется индекс уже несуществующего элемента. Его нужно убирать в главном потоке или он сам исчезнет? Вроде сам то не должен исчезнуть...

Еще хочу уточнить, куда помещать threads->exit() - по логике получается что как раз вместо return 0 . Если строкой ранее - то return окажется бесполезен (ошибаюсь?), а если следующей строкой - то завершение работы нити по exit попросту никогда не выполнится. Верно мыслю?

Про созданные внутри нити переменные, если можно, несколько слов. Их нужно индивидуально каждую прихлопнуть, или достаточно внутри sub client объявить их как my и они по threads->exit() прекратят свое существование, освободив память?
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38692572
White Owl
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vkleВ текущем варианте выход из sub client сделан через return 0 . В таком случае после выхода по return 0 в пеерменной $threads[$count] остается ставший ненужным экземпляр "клиента". Но ведь и после threads->exit() в $threads останется индекс уже несуществующего элемента. Его нужно убирать в главном потоке или он сам исчезнет? Вроде сам то не должен исчезнуть...Просто return 0 это нормально. threads->exit() используется для "неожиданного" завершения нити.
И поток это не процесс, он как раз должен исчезать сам. Если ты думаешь о zombie-процессах, то нет, в перловом варианте нитей этого нет.


vkleЕще хочу уточнить, куда помещать threads->exit() - по логике получается что как раз вместо return 0 . Если строкой ранее - то return окажется бесполезен (ошибаюсь?), а если следующей строкой - то завершение работы нити по exit попросту никогда не выполнится. Верно мыслю?Все верно.


vkleПро созданные внутри нити переменные, если можно, несколько слов. Их нужно индивидуально каждую прихлопнуть, или достаточно внутри sub client объявить их как my и они по threads->exit() прекратят свое существование, освободив память?Если это примитивные переменные, то my достаточно. А если это нечто созданное через somemodule->new, то недостаточно.
Си знаешь? Разницу между стековыми переменными и памятью на куче знаешь? Вот это оно и есть. my - стек, new() - куча.


Навскидку, в том огрызке что ты привел, я вижу `$dbh = DBI->connect() / $dbh->disconnect()` этого мало.
На DBI->connect() делается не только коннект к базе, но и инициализация библиотек которые собственно делают коннект. А dbh->disconnect() не обязан эти библиотеки выгружать. А вдруг ты два коннекта открыл к двум разным базам?
Плюс к этому, сами интерфейсные библиотеки могут делать кучу всякого разного, до чего никак не добраться снаружи.
И при всем этом, у DBI нету метода "выгрузить все".

В общем, я в первую очередь убрал бы отсюда всю работу с СУБД. Если я правильно понимаю задачу, то тебе вполне хватит отдавать в клиентскую нить хеш-массив который ты будешь создавать один раз при старте программы. Ну и если нужно, раз в час/сутки/год пусть главный поток отвлекается от ожидания клиентов и перечитывает список переадресации - перестраивает этот хеш-массив.

Если СУБД обязательно нужна внутри нити, то лучше будет вместо DBI использовать что-нибудь другое, более thread-friendly. В идеале вообще, лучше взять оригинальные интерфейсные библиотеки, и сделать свой модуль с оглядкой на их thread-safety как рекомендуют авторы СУБД. Ну или найти соответствующий модуль. Но DBI точно надо убрать нафиг.
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38693739
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
White OwlvkleВ текущем варианте выход из sub client сделан через return 0 . В таком случае после выхода по return 0 в пеерменной $threads[$count] остается ставший ненужным экземпляр "клиента". Но ведь и после threads->exit() в $threads останется индекс уже несуществующего элемента. Его нужно убирать в главном потоке или он сам исчезнет? Вроде сам то не должен исчезнуть...Просто return 0 это нормально. threads->exit() используется для "неожиданного" завершения нити.
И поток это не процесс, он как раз должен исчезать сам. Если ты думаешь о zombie-процессах, то нет, в перловом варианте нитей этого нет.Благодарю за развернутый ответ. Здесь я думаю не о зомби. Я думаю, что после завершения нити ссылка на нее (на эту нить) будет храниться в $threads[$count]. В каком месте делать для нее undef - вот что не понятно.

White OwlЕсли СУБД обязательно нужна внутри нити, то лучше будет вместо DBI использовать что-нибудь другое, более thread-friendly. В идеале вообще, лучше взять оригинальные интерфейсные библиотеки, и сделать свой модуль с оглядкой на их thread-safety как рекомендуют авторы СУБД. Ну или найти соответствующий модуль. Но DBI точно надо убрать нафиг.Тоже подумал что DBI как-то корявенько внутри нити. В текущей реализации на каждого клиента открывается новый коннект к БД. Выносить его в основную программу - как то не слишком надежно. Полагаю, что автор сделал коннект именно внутри нити на случай временной недоступности СУБД (в таком случае клиенту достаточно заново подключться когда СУБД будет доступна).
Можете порекомендовать какое-то thread-friendly решение или модуль?


Сейчас попробовал сделать изменения в скрипте:
- все переменные внутри sub client определил через my,
- return 0 заменил везде на threads->exit(),
- и к $dbh->disconnect везде добавил еще undef $dbh.
По результатам беглого тестирования никакого существенного изменения по отъеданию памяти не обнаружил - только растет. Видимо, этих мер недостаточно.
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38693785
White Owl
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vkleЯ думаю, что после завершения нити ссылка на нее (на эту нить) будет храниться в $threads[$count]. В каком месте делать для нее undef - вот что не понятно.Не надо делать, само сделается. Когда нить завершается она автоматом уходит из списка. Конечно если у тебя есть собственная переменная, в которой хранится ссылка на свежезапущенный поток, то надо будет вручную отслеживать когда ее чистить ($thr->is_running() спасет :))
Но список нитей которые ведет сам модуль threads обновляется (и чистится) автоматом.

vkleМожете порекомендовать какое-то thread-friendly решение или модуль?Это не ко мне. Я с MySQL практически не работаю. Но на cpan лежит куча MySQL модулей кроме DBD::MySQL.
Ну и можешь спросить на соответствующем форуме, как можно сделать потоко-безопасное подключение из Си и потом искать соответствующий модуль.
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38693841
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
White OwlКонечно если у тебя есть собственная переменная, в которой хранится ссылка на свежезапущенный поток, то надо будет вручную отслеживать когда ее чистить ($thr->is_running() спасет :))Хм, действительно, чего-то я тупанул... Переменная то для хранения нити есть, но она вроде как более нигде не используется. Завтра попробую убрать ее.
...
Рейтинг: 0 / 0
[perl] Каким образом освободить память?
    #38694150
vkle
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ан нет...
От строки
$threads[$count] = threads->new(\&client, $count, $CLIENTS[$count]);
оставил только правую часть - никаких изменений.
...
Рейтинг: 0 / 0
10 сообщений из 10, страница 1 из 1
Форумы / PHP, Perl, Python [игнор отключен] [закрыт для гостей] / [perl] Каким образом освободить память?
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]