Модераторы: Partizan, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> AppDomain. Ограничение памяти и времени выполнения, Нужна помощь знатоков AppDomain 
V
    Опции темы
kolaspirit
Дата 16.4.2014, 03:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 7.12.2011

Репутация: нет
Всего: нет



Добрый вечер. Постараюсь подробно описать проблему.
    Необходимо было реализовать "песочницу" (Sandbox) для автоматической проверки олимпиадных решений по программированию через домены приложений (AppDomain). То есть участник соревнования присылает исходник с решением задачи, моя программа компилирует его и запускает exe-файл с помощью AppDomain.ExecuteAssembly(<имя_сборки>) в мною созданном AppDomain, в котором уже выставлены разрешения (Permissions), ограничивающие сборку в правах. Например, чтобы нельзя было работать с реестром, файловой системой, создавать новые процессы, новые домены приложений и т.д. 
    Но я не смог найти ни на форумах, ни в MSDN информацию о том, как ограничить по времени выполнения и по выделению оперативной памяти запускаемую в домене сборку. Сейчас использую связку: AppDomain с его Permissions и  Process с возможностью ограничивать время выполнения. Но этот вариант не подходит, так как последовательно сначала сборка исполняется в AppDomain без учета времени и памяти (в этот момент может произойти переполнение или же программа может "вечно" работать), а потом уже, по завершении, запускается в Process. Нужно чтобы все ограничения были в рамках AppDomain. Ниже приведен код. Подскажите, пожалуйста, как решить этот вопрос? Или же подскажите другие варианты, сочетающиеся с AppDomain (от доменов приложений отказываться не вижу смысла, ибо в них есть большая часть необходимых мне ограничений). Не исключаю, что решение может быть простым. Заранее прошу прощения, если его не разглядел.

Сам класс "песочницы" (убраны не относящиеся к теме методы и свойства):
Код

public class Sandbox
{
    private AppDomain SandboxAppDomain { get; set; }
    private PermissionSet SandboxPermissionSet { get; set; }

    public Sandbox(string workingDirectory, PermissionSet permissionSet)
    {
        SandboxPermissionSet = permissionSet;
        AppDomainSetup sandboxAppDomainSetup = new AppDomainSetup();
        sandboxAppDomainSetup.ApplicationBase = workindDirectory;
        SandboxAppDomain = AppDomain.CreateDomain("Sandbox", new Evidence(), sandboxAppDomainSetup, SandboxPermissionSet);
    }

    public void ExecuteAssembly(string assemblyFile)
    {
        SandboxAppDomain.ExecuteAssembly(assemblyFile);
    } 
}


Вызов ExecuteAssembly из кода:
Код

private string InputFileName { get; set; }
private string OutputFileName { get; set; }
private string ExecutableName { get; set; }

private bool RunInSandbox(string workingDirectory, int timeLimit)
{
    string fileNameArg = System.IO.Path.Combine(workingDirectory, ExecutableName + ".exe");

    var tempInput = System.IO.Path.Combine(workingDirectory, InputFileName);
    var tempOutput = System.IO.Path.Combine(workingDirectory, OutputFileName);

    // изначально запрещаем все AppDomain'у
    PermissionSet permissionSet = new PermissionSet(PermissionState.None);

    // добавляем необходимые разрешения
    permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
    permissionSet.AddPermission(new UIPermission(PermissionState.Unrestricted));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery, workingDirectory));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, tempInput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Write, tempOutput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Append, tempOutput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, fileNameArg));

    // создаем новый домен приложений с выставленными разрешениями
    Sandbox sandbox = new Sandbox(workingDirectory, permissionSet);

    // загружаем сборку в домен и исполняем
    sandbox.ExecuteAssembly(fileNameArg);

    // выгружаем домен
    sandbox.Unload();

    // а теперь проверяем сборку на ограничение по времени (после того, как она завершит исполняться в AppDomain)
    var startInfo = new ProcessStartInfo
    {
            FileName = fileNameArg,
            CreateNoWindow = true,
            UseShellExecute = false,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = workingDirectory,
            ErrorDialog = false,
            LoadUserProfile = false,
    };

    var process = new Process();
    process.StartInfo = startInfo;

    if (process.Start())
    {
        if (process.WaitForExit(timeLimit * 1000))
        {
            return true;
        }
        else
        {   
            process.Kill();
            if (!process.HasExited)
            {
                process.WaitForExit();
            }
        }
    }
    return false;
}


Это сообщение отредактировал(а) kolaspirit - 16.4.2014, 04:02
PM MAIL   Вверх
jonie
Дата 16.4.2014, 10:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



http://www.c-sharpcorner.com/UploadFile/ha...-in-dotnet-4-0/

а сам "чужой" код вы можете запускать в потоке, который по таймауту убивать...

но по-мне так проще делать суррогатный процесс, имхо так проще чистить ресурсы..


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
kolaspirit
Дата 15.5.2014, 15:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 7.12.2011

Репутация: нет
Всего: нет



Спасибо. Проблема решена.

Это сообщение отредактировал(а) kolaspirit - 15.5.2014, 19:20
PM MAIL   Вверх
jonie
Дата 15.5.2014, 20:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



Цитата(kolaspirit @ 15.5.2014,  16:37)
Спасибо. Проблема решена.

и как решили?


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
kolaspirit
Дата 15.5.2014, 21:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 7.12.2011

Репутация: нет
Всего: нет



Следующим образом:

Код

private string InputFileName { get; set; }
private string OutputFileName { get; set; }
private string ExecutableName { get; set; }
// ограничение по времени (в секундах)
private int TimeLimit {get ; set; }
// ограничение по памяти (в байтах)
private int MaxMemorySize { get; set; }

private bool RunInSandbox(string workingDirectory, int timeLimit)
{
    string fileNameArg = System.IO.Path.Combine(workingDirectory, ExecutableName + ".exe");

    var tempInput = System.IO.Path.Combine(workingDirectory, InputFileName);
    var tempOutput = System.IO.Path.Combine(workingDirectory, OutputFileName);

    // изначально запрещаем все AppDomain'у
    PermissionSet permissionSet = new PermissionSet(PermissionState.None);

    // добавляем необходимые разрешения
    permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
    permissionSet.AddPermission(new UIPermission(PermissionState.Unrestricted));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery, workingDirectory));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, tempInput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Write, tempOutput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Append, tempOutput));
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, fileNameArg));

    // включаем мониторинг ЦП и памяти доменов приложений для данного процесса
    AppDomain.MonitoringIsEnabled = true;
    
    // создаем новый домен приложений с выставленными разрешениями
    Sandbox sandbox = new Sandbox(workingDirectory, permissionSet);

    // заводим переменную Exception для хранения в основном потоке тех исключений, которые возникают во вторичном потоке
    Exception threadException = null;
    var newThread = new Thread(() =>
    {
        try
        {
             // запускаем сборку с "чужим" кодом на исполнение
             sandbox.ExecuteAssembly(fileNameArg);
        }
        catch (Exception ex)
        {
             // если во вторичном потоке возникло исключение, то запоминаем его
             threadException = ex;
        }
    });
     
    newThread.Start();

    // задаем тайм-аут для созданного потока. Если время превышено, выдаем исключение
    if (!newThread.Join(timeLimit * 1000))
    {
         sandbox.Unload();
         throw new TimeoutException("Время выполнения программы было превышено.");
    }
         
    // если во вторичном потоке возникло исключение, то threadException не будет пуст    
    if (threadException != null)
    {
        sandbox.Unload();
        throw new Exception(threadException.Message);
    }
    
    // проверяем на превышение предела выделенной памяти. Если превышен, - выдаем исключение       
    if (sandbox.GetTotalAllocatedMemorySize() > MaxMemorySize)
    {
        sandbox.Unload();
        throw new Exception("Превышен допустимый предел выделяемой приложением памяти.");
    }
    
    sandbox.Unload();
    return true;
}


При этом в класс Sandbox был добавлен один метод:

Код

public long GetTotalAllocatedMemorySize()
{
    return SandboxAppDomain.MonitoringTotalAllocatedMemorySize;
}


Остается только один вопрос: как мониторить кол-во выделяемой сборкой памяти, пока эта сборка исполняется? Сейчас "проверка на память" происходит после завершения работы потока, что не есть хорошо. 

Это сообщение отредактировал(а) kolaspirit - 15.5.2014, 21:16
PM MAIL   Вверх
jonie
Дата 15.5.2014, 21:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



Цитата(kolaspirit @  15.5.2014,  22:11 Найти цитируемый пост)

Остается только один вопрос: как мониторить кол-во выделяемой сборкой памяти, пока эта сборка исполняется? Сейчас "проверка на память" происходит после завершения работы потока, что не есть хорошо. 

поток завести (например в новом домене) и while(true) { смотреть на память } ?)


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
kolaspirit
Дата 15.5.2014, 21:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 7.12.2011

Репутация: нет
Всего: нет



Цитата

поток завести (например в новом домене) и while(true) { смотреть на память } ?)


    Благодарю, уже думал об этом) Но это даже не проблема, решается парой строчек кода. Сейчас думаю над тем, как организовать очередь на проверку решений. Дело в том, что каждый участник, присылая решение на проверку, вызывает свой экземпляр dll-библиотеки, в которой содержатся все эти классы (для проверки). Мне необходимо сделать общую для всех очередь, например, чтобы одновременно могло проверяться не более 5 присланных решений, остальные должны дожидаться, находясь в этой очереди.
    Пока не копал глубоко, скорей всего решение в скором времени найдется. Однако, если есть здравые соображения по этому поводу, буду весьма признателен.

   Если в скором времени решение не найду, скорей всего, вынесу в отдельную тему. Вопрос сам по себе интересный.

Это сообщение отредактировал(а) kolaspirit - 15.5.2014, 21:57
PM MAIL   Вверх
jonie
Дата 16.5.2014, 09:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



Цитата(kolaspirit @  15.5.2014,  22:54 Найти цитируемый пост)
Мне необходимо сделать общую для всех очередь, например, чтобы одновременно могло проверяться не более 5 присланных решений, остальные должны дожидаться, находясь в этой очереди.

ну заведите очередь "задач на проверку" синхронизированную  семафором


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
kolaspirit
Дата 16.5.2014, 10:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 10
Регистрация: 7.12.2011

Репутация: нет
Всего: нет



Цитата

ну заведите очередь "задач на проверку" синхронизированную  семафором

   Спасибо за совет, буду разбираться. До этого с семафорами только вскользь был знаком)

Это сообщение отредактировал(а) kolaspirit - 16.5.2014, 10:27
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
mr.DUDA
THandle

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »


 




[ Время генерации скрипта: 0.0781 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.