powered by simpleCommunicator - 2.0.38     © 2025 Programmizd 02
Форумы / ASP.NET [игнор отключен] [закрыт для гостей] / Память не освобождается
25 сообщений из 28, страница 1 из 2
Память не освобождается
    #40018524
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Всем привет, решил написать рутрекер небольшой файлообменник для локальных нужд. Из браузера post-запросом отправляется файл с "Content-Type": "multipart/form-data", на бэкэнде AspCore его принимаем, Zip'уем его, присваиваем идентификатор и сохраняем на диск, чтобы потом можно было вернуть.
Я протестировал на файле большого объема - 460мб, проблема в том, что после каждого вызова метода контроллера отъедается дополнительная память и не освобождается.
Статистика динамики потребления памяти приложения при загрузке вышеуказанного файла такая:
1-вызов+152мб к текущему уровню потребления
2-вызов+228мб к текущему уровню потребления
3-вызов+141мб к текущему уровню потребления
Метод отрабатывает корректно и потребление памяти при выходе из контроллера не падает вообще никак.
Не могу понять какой объект держит память.
Код: c#
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.
74.
75.
76.
77.
78.
79.
80.
81.
82.
        [RequestSizeLimit(600_000_000)]
        [HttpPost]
        [DisableFormValueModelBinding]
        /// <summary>
        /// Загрузить файлы на жесткий диск
        /// </summary>
        /// <returns></returns>
        public async Task<ApiDataResponse> UploadFilesToDrive()
        {
            try
            {
                var boundary = GetBoundary(Request.ContentType);
                var reader = new MultipartReader(boundary, Request.Body, 80 * 1024);

                MultipartSection section;
                using var zippedStream = new MemoryStream();

                using (var zipArchive = new ZipArchive(zippedStream, ZipArchiveMode.Create, true))
                {
                    string fileName = string.Empty;
                    while ((section = await reader.ReadNextSectionAsync()) != null)
                    {
                        var contentDispo = section.GetContentDispositionHeader();
                        fileName = contentDispo.FileName.ToString();

                        if (contentDispo.IsFileDisposition())
                        {
                            var fileSection = section.AsFileSection();
                            var zipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);
                   
                            using (var zipEntryStream = zipEntry.Open())
                            {
                                await fileSection.FileStream.CopyToAsync(zipEntryStream); // на этой строке потребление памяти значительно растет:+60мб для каждого вызова
                            }
                        }
                        else if (contentDispo.IsFormDisposition())
                        {
                            return new ErrorApiResponse("IsFormDisposition");
                        }
                    }
                }
                zippedStream.Seek(0, SeekOrigin.Begin);
                string newFileName = DateTime.Now.Ticks + "_" + Guid.NewGuid().ToString();

                using (FileStream outputFileStream = System.IO.File.Create($"FileStorage/{newFileName}.zip"))
                {
                    zippedStream.CopyTo(outputFileStream);
                }

                return new ApiDataResponse(newFileName);
            }
            catch (System.Exception ex)
            {
                return new ErrorApiResponse(ex.Message);
            }
        }


 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class DisableFormValueModelBindingAttribute : FlagsAttribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var factories = context.ValueProviderFactories;
            factories.RemoveType<FormValueProviderFactory>();
            factories.RemoveType<JQueryFormValueProviderFactory>();
        }
    }

    private static string GetBoundary(string contentType)
        {
            if (contentType == null)
                throw new ArgumentNullException(nameof(contentType));

            var elements = contentType.Split(' ');
            var element = elements.First(entry => entry.StartsWith("boundary="));
            var boundary = element.Substring("boundary=".Length);

            boundary = HeaderUtilities.RemoveQuotes(boundary).ToString();

            return boundary;
        }


Сравнил два снимка памяти-при только что запущенном приложении и когда уже несколько раз прогнал загрузку файла.
Наибольшая разница по размеру составляет "AsyncTaskMethodBuilder"- то есть как будто я запускаю очень много Task, что не соответствует действительности. Подскажите по сабжу плиз. Спасибо
...
Рейтинг: 0 / 0
Память не освобождается
    #40018536
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Попробуй на время в конце вставить вызов GC.Collect(). Если ситуация не изменится, то тогда у тебя где-то действительно проблемы с утечкой. А если будет освобождаться, то, утечки на самом деле нет, и копать можно только в сторону какой-нибудь оптимизации. По поводу оптимизации, поищи в интернете, как в ASP.NET паковать архив на лету сразу в HttpResponse без временного хранения в памяти - про это есть статьи, я точно помню, потому что года полтора назад у самого была такая задача.
...
Рейтинг: 0 / 0
Память не освобождается
    #40018633
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub,

Нельзя так работать с мультипартом.
Сбросьте всё на диск во временную папку как можно быстрее.
Затем работайте с файлами из папки.
...
Рейтинг: 0 / 0
Память не освобождается
    #40018634
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub,

Плюс, зипуйте также сразу на диск в режиме потока.
Пострайтесь избегать использование памяти.

Не используйте MemoryStream, чтобы потом не жаловаться на утечки памяти :)
Если всё же приходится, лучше переиспользовать память, для этого есть решения -- легко гуглятся.
...
Рейтинг: 0 / 0
Память не освобождается
    #40018921
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt,
что конкретно у меня неправильного в операциях с мультипартом?
...
Рейтинг: 0 / 0
Память не освобождается
    #40018982
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
hVostt,
что конкретно у меня неправильного в операциях с мультипартом?


Посмотрите сюда: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming-1

и конечно же, не используйте MemoryStream
...
Рейтинг: 0 / 0
Память не освобождается
    #40019399
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Получилось все сделать без утечек и с минимальной нагрузкой на память-1 вызов метода контроллера для файла 460мб занимает 0,5 мб, потом освобождется.
Всем спасибо за участие.
Код: c#
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.
            try
            {
                var boundary = GetBoundary(Request.ContentType);
                var reader = new MultipartReader(boundary, Request.Body, 80 * 1024);

                MultipartSection section;
     
                string newFileName = DateTime.Now.Ticks + "_" + Guid.NewGuid().ToString();

                using (FileStream outputFileStream = System.IO.File.Create($"FileStorage/{newFileName}.zip"))
                {
                    using (var zipArchive = new ZipArchive(outputFileStream, ZipArchiveMode.Create, true))
                    {
                        string fileName = string.Empty;
                        while ((section = await reader.ReadNextSectionAsync()) != null)
                        {
                            var contentDispo = section.GetContentDispositionHeader();
                            fileName = contentDispo.FileName.ToString();

                            if (contentDispo.IsFileDisposition())
                            {
                                var fileSection = section.AsFileSection();
                                var zipEntry = zipArchive.CreateEntry(fileName, CompressionLevel.Optimal);

                                using (var zipEntryStream = zipEntry.Open())
                                {
                                    await fileSection.FileStream.CopyToAsync(zipEntryStream); 
                                }
                            }
                            else if (contentDispo.IsFormDisposition())
                            {
                                return new ErrorApiResponse("IsFormDisposition");
                            }
                        }
                    }
                }

                return new ApiDataResponse(newFileName);
            }
            catch (System.Exception ex)
            {
                return new ErrorApiResponse(ex.Message);
            }
...
Рейтинг: 0 / 0
Память не освобождается
    #40019402
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt
vb_sub
hVostt,
что конкретно у меня неправильного в операциях с мультипартом?


Посмотрите сюда: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1#upload-large-files-with-streaming-1

и конечно же, не используйте MemoryStream

К сожалению браузерные http-клиенты типа Axios не могут стримить файлы.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019446
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
К сожалению браузерные http-клиенты типа Axios не могут стримить файлы.

При чем тут клиент? У тебя body и запросов и ответов всегда доступно как stream - вот и надо работать с ним напрямую, а не загружать все в память.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019541
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
Код: c#
1.
2.
3.
4.
catch (System.Exception ex)
            {
                return new ErrorApiResponse(ex.Message);
            }



А какой в этом смысл? :)
...
Рейтинг: 0 / 0
Память не освобождается
    #40019545
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt
vb_sub
Код: c#
1.
2.
3.
4.
catch (System.Exception ex)
            {
                return new ErrorApiResponse(ex.Message);
            }



А какой в этом смысл? :)

Это широко здесь не любимая обертка для http-ответов, но очень удобная
Код: c#
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.
 /// <summary>
    ///Обертка для ответа клиенту
    /// </summary>
    public class ApiDataResponse
    {
        /// <summary>
        /// Response payload
        /// </summary>
        public object Body { get; }

        public bool IsSuccess => Errors == null;

        protected IList<string> Errors;

        /// <summary>
        /// Добавление ошибок
        /// </summary>
        /// <param name="errors">Список ошибок</param>
        public void AddErrors(IEnumerable<string> errors)
        {
            if (Errors == null)
                Errors = new List<string>();

            foreach (var error in errors)
                Errors.Add(error);
        }

        public ApiDataResponse(object body = null)
        {
            Body = body;
        }
    }

    /// <summary>
    /// Запрос завершился с ошибкой
    /// </summary>
    public class ErrorApiResponse : ApiDataResponse
    {
        public ErrorApiResponse(string error) : base(null)
        {
            AddErrors(new string[1] { error });
        }

        /// <summary>
        /// Ошибки
        /// </summary>
        public string Error => Errors.Aggregate((a, b) => $"{a}{Environment.NewLine}{b}");
    }

    public class ApiDataResponse<T> : ApiDataResponse
    {
        public ApiDataResponse(T body) : base(body)
        {
        }
    }
...
Рейтинг: 0 / 0
Память не освобождается
    #40019548
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
Это широко здесь не любимая обертка для http-ответов, но очень удобная


Да пофиг на обёртку. Вы каждом действии контроллера делаете catch(...) ? :)
...
Рейтинг: 0 / 0
Память не освобождается
    #40019550
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Память не освобождается
    #40019559
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt,
спасибо за ссылку, давно думал сделать базовый контроллер типа
Код: c#
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.
   [ApiController, Route("api/[controller]/[action]")]
    public class BaseController : ControllerBase
    {
        public async Task<ApiDataResponse> ControllerAction(Action action)
        {
            try
            {
                action.Invoke();
                return new ApiDataResponse();
            }
            catch (Exception ex)
            {
                return new ErrorApiResponse(ex.Message);
            }
        }
    }


    public class RegularController: BaseController
    {
        public async Task<ApiDataResponse> Action()
        {
            Action action = () => { Console.WriteLine("In Action"); };
            return await base.ControllerAction(action);
        }
    }


но никак не мог собраться протестировать не просядет ли перфоманс из-за делегатов, но вариант по ссылке хорош.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019562
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt
vb_sub
Это широко здесь не любимая обертка для http-ответов, но очень удобная


Да пофиг на обёртку. Вы каждом действии контроллера делаете catch(...) ? :)

Да, до Вашей ссылке так и делал.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019572
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
вроде как в серверном профайле управления памятью GC предполагается, что память выделяет и не освобождается немедленно, так как вероятнее всего будет скоро снова востребована .
То есть памяти программе может быть выделено куча, а использовано c гулькин нос.
А так как вы используйте MemoryStream, который скорее всего попадет в ЛоХ, то когда эта память освободится одному богу известно.
Почему нельзя сразу писать поток в файл по месту назначения?
...
Рейтинг: 0 / 0
Память не освобождается
    #40019573
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Roman Mejtes,
со второго раза так и сделал-все получилось.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019591
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник

ASP.NET и так заворачивает весь pipeline в try-catch, превращающий любой непойманый exception в http status 500. Начиная с версии 3 для Api-контроллеров стал к тому же нормально работать UseDeveloperExceptionPage, возвращая красивый JSON с информацией по исключению. До этого он выплевывал в response HTML, который во всяких dev-tools и postman было неудобно читать. Я в те времена использовал всегда свой кастомный фильтр для этого - где-то на гитхабе до сих пор лежит.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019804
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
ASP.NET и так заворачивает весь pipeline в try-catch, превращающий любой непойманый exception в http status 500. Начиная с версии 3 для Api-контроллеров стал к тому же нормально работать UseDeveloperExceptionPage, возвращая красивый JSON с информацией по исключению. До этого он выплевывал в response HTML, который во всяких dev-tools и postman было неудобно читать. Я в те времена использовал всегда свой кастомный фильтр для этого - где-то на гитхабе до сих пор лежит.


Ну да, об этом и говорю. Однако у ТС свой способ обработки исключений, по ссылке как раз примерчик, как это реализовать. Также можно ещё фильтр на контроллер сделать, что скорее всего будет более корректным решением.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019818
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
hVostt
у ТС свой способ обработки исключений

И на*я зачем? Чтобы другим (особенно новому человеку) по полдня потом долбаться, разбираясь, почему, например, вместо 500 возвращается 200, или почему стектрейс исключения в консоли/логах не виден? Задрали же уже все эти изобретения ИТ-самоделкиных.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019835
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
hVostt
у ТС свой способ обработки исключений

И на*я зачем? Чтобы другим (особенно новому человеку) по полдня потом долбаться, разбираясь, почему, например, вместо 500 возвращается 200, или почему стектрейс исключения в консоли/логах не виден? Задрали же уже все эти изобретения ИТ-самоделкиных.

Нужно возвращать соответствующий статускод http-запроса?
...
Рейтинг: 0 / 0
Память не освобождается
    #40019881
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
Нужно возвращать соответствующий статускод http-запроса?

И какой же статускод вернется после такого:
автор
Код: c#
1.
2.
3.
4.
 catch (Exception ex)
 {
      return new ErrorApiResponse(ex.Message);
 }



Про "потерянный" стектрейс и прочее я уже даже и не говорю.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019943
vb_sub
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat,
статус код всегда 200 и в случае успеха и в случае ошибки.
...
Рейтинг: 0 / 0
Память не освобождается
    #40019953
fkthat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
vb_sub
fkthat,
статус код всегда 200 и в случае успеха и в случае ошибки.

Ok.
...
Рейтинг: 0 / 0
Память не освобождается
    #40020115
Фотография hVostt
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
fkthat
hVostt
у ТС свой способ обработки исключений

И на*я зачем? Чтобы другим (особенно новому человеку) по полдня потом долбаться, разбираясь, почему, например, вместо 500 возвращается 200, или почему стектрейс исключения в консоли/логах не виден? Задрали же уже все эти изобретения ИТ-самоделкиных.


Ну хз, чтоб потом его админы материли наверное :)
И те, кто будут сопровождать дальше код.

Ну нравится так человеку, что поделаешь.
...
Рейтинг: 0 / 0
25 сообщений из 28, страница 1 из 2
Форумы / ASP.NET [игнор отключен] [закрыт для гостей] / Память не освобождается
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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