...

суббота, 10 октября 2015 г.

Huawei и NTT DOCOMO успешно провели испытания технологии 5G в полевых условиях

Китайская компания Huawei и японский телекоммуникационный гигант NTT DOCOMO успешно провели первые в мире масштабные испытания беспроводной передачи данных по технологии 5G, используя суб-6ГГц диапазон. Специалисты смогли передать данные со скоростью 3,6 гбит/сек.

Эксперты из International Telecommunication Union определили верхнюю границу скорости передачи данных в сети 5G в 20 Гбит/сек, но достичь такой пропускной способности довольно тяжело, так что и 3,6 Гбит/сек считается отличным результатом. Текущий тест отличается от всех прочих экспериментов своей масштабностью — дело в том, что до настоящего момента тестирование технологии проводилось в лабораторных условиях.

При этом в других экспериментах специалисты использовали для передачи данных другой спектральный диапазон, позволяющий передавать данные с большей скоростью, но только на открытой местности. Стены и прочие препятствия оказались практически непрозрачными для такого сигнала.
Например, испытания, которые проводились Samsung, позволили передавать данные со скоростью 7,5 Гбит/сек в лабораторных условиях. При проведении аналогичных испытаний в движении скорость упала до 1,2 Гбит/сек, причем скорость станции с приемным оборудованием составляла всего около 100 км/ч на отрезке пути в 4,35 км. Эти испытания проводились со спектром в 28 ГГц. Nokia Networks удалось передать данные со скоростью в 10 Гбит/сек, но снова-таки, испытания проводились в диапазоне 73 ГГц.

Очевидная проблема работы с подобными частотами — необходимость создания нового типа сетей, с разработкой нового оборудования, что может быть достаточно затратной задачей. А Huawei удалось передать данные, используя частоту в суб-6ГГц диапазоне, которая достаточно широко используется.

Полевые тесты проводились на открытой местности в Китае, с использованием таких технологий передачи данных, как Multi-User MIMO, Sparse Code Multiple Access (SCMA) and Filtered OFDM (F-OFDM).

Пропускная способность нисходящего канала при этом составила 1,34 Гбит/сек, с максимальным значением в 3,6 Гбит/сек. При этом Huawei работала также с 5G SU-MIMO прототипом, с пиковой пропускной способностью в 10 Гбит/сек при частоте 200 МГц.

«Впервые в мире удалось удачно провести подобное тестирование, и это является важным событием. Частоты 5G планируется использовать в коммерческих целях уже к 2020 году. Huawei и DOCOMO смогли достичь успеха, и теперь следующий этап испытаний будет проведен в Японии», сообщил вице-президент DOCOMO.

«Объединенные усилия наших команд позволили достичь значительного успеха, что позволит обеспечить выполнение плана Huawei по разработке технологических стандартов для 5G со стороны Huawei до 2018 года. Результаты подобные этому показывают, насколько быстро мы движемся. Я уверен, что мы достигнем и больших успехов, если продолжим работу с 5G», — прокомментировал ситуаци СТО Huawei Wireless Вен Тонг.

Сейчас компания Huawei планирует запустить пилотные сети 5G вместе со своими партнерами, причем случиться это должно уже в 2018 году. Глобальное внедрение стандарта 5G намечается на 2020 год.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

И решили мы подружить Baresip и Nodejs

Что же такое Baresip? Как сказано на сайте проекта это кроссплатформенный, модульный SIP агент с поддрежкой аудио и видео.

А причем тут nodejs? Да так вышло что в большом проекте написанном на nodejs и использующем Freeswitch и его модуль portaudio в связи с некорректной работой последнего потребовалось внедрить другой sip клиент для аудио звонков.

Выбор в ходе технического совещания одного из наших разработчиков с гуглом пал на Baresip.

Основные задачи:


  • Управление звонками(позвонить, положить трубку, ответить и т.д.)
  • Получение событий(входящий звонок, звонок завершился, дозвонились и т.д.)
  • Получение информации о состоянии клиента(список звонков и т.д.)

В результате получился небольшой npm модуль node_baresip

Ссылка на страницу baresip

Поиск api


Так как baresip консольная утилита я решил что наверника есть или библиотека или какойто способ взаимодействия с запущенным приложением.
И я был прав но не до конца :)

Как оказалось в baresip все завязано на текстовые команды и их можно ввести через:

  • консоль
  • get параметр http запроса
  • UDP/TCP соединение

Настоящий хардкорный api :)

Для первой реализации я выбрал http.

Реализация node_baresip


Отправка команд будет выглядеть так:
Звонок в консоли
d sip:user@domain.com

Звнок через http
http://ift.tt/1G34KNv

Был написан baresiphttpwrapper.js с обобщенным интерфейсом:

  • getCallList
  • dial
  • hangup
  • answer
  • muteUnmute
  • hold
  • resume

В последствии можно либо написать нормальный api и сделать другой wrapper либо дождаться когда кто-то его напишет и переписать wrapper :)

Далее реализован основной файл модуля baresip.js

Для отслеживания состояния приходиться запрашивать список звонков каждые 2 секунды и определять какие звонки старые, какие были заверешны, какие исходящие дозвонились и какие входящие и выдавать соответсвующие события:

Код
self.intervalObject = setInterval( function(){
            self.baresipWrapper.getCallList( function( err, callList){
                                if( err){
                    console.log( "Error get call list:", err, config);
                    var callList = [];
                }
                                var esatblishedCalls = self.findEsatblishedCalls( callList);
                                var newCalls = self.findNewCalls( callList);
                                var deleteCalls = self.findDeletedCalls( callList);

                for (var i in newCalls){
                    // ADD CALL
                    self.callList[newCalls[i].sip] = newCalls[i];
                    self.callList[newCalls[i].sip].id = self.generateCallId();

                    self.emit( "new_call", self.callList[newCalls[i].sip]);

                    if( newCalls[i].status == "INCOMING") {
                        self.emit("incoming_call", newCalls[i]);
                    }
                    else if( newCalls[i].status == "ESTABLISHED"){
                        self.emit( "established_call", self.callList[newCalls[i].sip]);
                    }
                }

                for (var i in deleteCalls){
                    delete self.callList[deleteCalls[i].sip];
                    self.emit( "end_call", deleteCalls[i]);
                }

                for( var i in esatblishedCalls) {
                    self.callList[esatblishedCalls[i].sip].status = "ESTABLISHED";
                    self.emit( "established_call", esatblishedCalls[i]);
                }
                        });
                }, self.callListMonitorTimeout);



События
  • new_call
  • established_call
  • end_call

Интерефейс управления я сделал практически такой же как во wrapper-е
  • getCurrentCalls
  • dial
  • hangup
  • answer
  • muteUnmute
  • hold
  • resume

Веселые сюрпризы baresip


  • Как оказалось в baresip у звонка нет никакого идентификатора следовательно и управлять им невозможно.
    Все управление сделано только над последним звонком добавленным в с-шный список звонков в baresip. Доходит до того что когда приходит входящий звонок и уже есть актиынй зконок управлять можно только входящим а активный нельзя даже поставить на hold.
  • Одновременно может быть активно сколько угодно звонков, нет автоматической постановки активного звонка на hold при ответе на новый входящий и даже нет возможности опционально задавать это в конфиге.
  • Можно сделать сколько угодно звонков одному и тому же абоненту.

Если последние 2 можно решить простыми проверками то первая заняла определенное время.
Сначало я попытался ввести в код baresip идентификатор звонка для возможности управления любым звонком через api, но так как Си я знаю плохо реализовать кроссплатформенный код который может генерировать уникальные идентификаторы я не смог.
Было принято более простое решение. Я добавил команду постановки предыдущего звонка на hold ее даже добавили в сам код baresip. Это мелкая правка решила стоявшие проблемы и дальше углубляться не пришлось.

Впечатления


Сам baresip оказался очень даже надежным кроссплатформенным sip агент с большим набором кодеков и различных модулей, вот только интерфейс взаимодействия подкачал :)

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Async/await и механизм реализации в C# 5.0


Механизм async реализован в компиляторе C# при поддержке со стороны библиотек базовых классов .NET. В саму исполняющую среду не пришлось вносить никаких изменений. Это означает, что ключевое слово await реализовано путем преобразования к виду, который мы могли бы написать и сами в предыдущих версиях C#. Для изучения генерируемого кода можно воспользоваться декомпилятором .NET Reflector или ILSpy. Это не только интересно, но и полезно для отладки, анализа производительности и других видов диагностики асинхронного кода.

Метод заглушка


Рассмотрим сперва простой пример асинхронного метода:
public async Task<Int32> MethodTaskAsync()
        {
            Int32 one = 33;
            await Task.Delay(1000);
            return one;
        }


Данный пример довольно прост, но достаточно практичен и удобен для объяснения основного принципа реализации async/await. Запустим ILSpy и изучим код, который автоматически формирует компилятор C#:
         [AsyncStateMachine(typeof(Program.<MethodTaskAsync>d__0))]
                public Task<int> MethodTaskAsync()
                {
                        Program.<MethodTaskAsync>d__0 <MethodTaskAsync>d__ = new Program.<MethodTaskAsync>d__0();
                        <MethodTaskAsync>d__.<>4__this = this;
                        <MethodTaskAsync>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
                        <MethodTask>d__.<>1__state = -1;
                        AsyncTaskMethodBuilder<int> <>t__builder = <MethodTaskAsync>d__.<>t__builder;
                        <>t__builder.Start<Program.<MethodTaskAsync>d__0>(ref <MethodTaskAsync>d__);
                        return <MethodTaskAsync>d__.<>t__builder.Task;
                }


Интересно, не правда ли? Ключевое слово async не оказывает никакого воздействия на то, как метод используется извне. Это заметно по тому, что сигнатура метода, сформированного компилятором, соответствует оригинальному методу за исключением слова async. В какой-то мере спецификатор async не считается частью сигнатуры метода, к примеру, когда речь заходит о переопределении виртуальных методов, реализации интерфейса или вызове.

Единственное назначение ключевого слова async — изменить способ компиляции соответствующего метода, на взаимодействие с окружением оно не оказывает никакого влияния. Также отметим, что в «новом» методе нет следов оригинального кода.

Структура конечного автомата


В приведенном выше примере компилятор автоматически применил к методу атрибут AsyncStateMachine. Когда метод (MethodTaskAsync) имеет модификатор async, компилятор генерирует IL включающий структуру конечного автомата.

Эта структура содержит код в методе. Код IL также содержит метод–заглушку (MethodTaskAsync), вызываемый в конечном автомате. Компилятор добавляет атрибут AsyncStateMachine к методу–заглушке, чтобы можно было идентифицировать соответствующий конечный автомат. Это необходимо для того, чтобы сделать объект, способный сохранить состояние метода в тот момент, когда программа дойдет до await. Ведь, как известно, код до этого ключевого слова выполняется в вызывающем потоке, а затем при его достижении сохраняется информация о том, в каком месте метода находилась программа, чтобы при возобновлении программа могла продолжить выполнение.

Компилятор мог бы поступить и по-другому: просто сохранить все переменные метода. Но в таком случае пришлось бы сгенерировать много кода. Однако, можно поступить иначе, а именно — просто создать экземпляр некоторого типа и сохранить все данные метода в виде членов данного объекта. Тогда при сохранении данного объекта автоматически будут сохранены все локальные переменные метода. Для этого и предназначена формируемая структура, называемая конечным автоматом (Finite State Machine).

Если вкратце, конечный автомат — это абстрактный автомат, число возможных внутренних состояний которого конечно. Грубо говоря, конечный автомат глазами пользователя – это черный ящик, в который можно что-то передать и что-то оттуда получить. Это очень удобная абстракция, которая позволяет скрыть сложный алгоритм, кроме того конечные автоматы очень эффективны. При этом имеется конечное множество входных символов, из которого формируются выходные слова. Также следует учитывать то, что каждый входной символ переводит автомат в новое состояние. В нашем случае в качестве входных символов будет выступать состояние выполнения асинхронной операции и уже на основе этого значения конечный автомат будет формировать некоторое состояние и соответственно реакцию на выполнение задачи(выходное слово). Данный подход упрощает формирование и управления асинхронными задачами.Более подробно про конечные автоматы можно найти в интернете с кучей подробных статей.

Конечный автомат формируется в виде класса и содержит следующие переменные-члены:

    public int32 '<>1__state';
    private int32 '<one>5__1';
    public Mechanism_async.Program '<>4__this';
    public System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder';
    private System.Runtime.CompilerServices.TaskAwaiter '<>u__awaiter';


Имена всех переменных содержат угловые скобки, показывающие, что имена сгенерированы компилятором. Это нужно для того, чтобы сгенерированный код не вступал в конфликт с пользовательским, — ведь в корректной программе на C# имена переменных не могут содержать угловых скобок.
  • В первой переменной <>1_state сохраняется номер достигнутого оператора await. Пока не встретился никакой await, значение этой переменной равно -1. Все операторы await в оригинальном методе пронумерованы, и в момент приостановки в переменную state заносится номер await, после которого нужно будет возобновить исполнение.
  • Следующая переменная <one>5_1 служит для хранения оригинальной переменной one. В генерированном компилятором коде все обращения к этой переменной заменены на обращение к этой переменной-члену.
  • Далее встречается переменная <>4_this. Она встречается только в конечных автоматах для нестатических асинхронных методов и содержит объект, от имени которого он вызывался. В каком-то смысле this — это просто еще одна локальная переменная метода, только она используется для доступа к другим переменным членам того же объекта. В процессе преобразования async-метода ее необходимо сохранить и использовать явно, потому что код оригинального объекта перенесен в структуру конечного автомата.
  • AsyncTaskMethodBuilder(<>t__builder) — представляет построитель для асинхронных методов, которые возвращают задачу. Этот вспомогательный тип и его члены предназначены для использования компилятором. Здесь инкапсулирована логика, общая для всех конечных автоматов. Именно этот тип создает объект Task, возвращаемых заглушкой. На самом деле этот тип очень похож на класс TaskCompletionSource в том смысле, что он создает задачу-марионетку, которую можно сделать завершенной позже. Отличие от TaskCompletionSource заключается в том, что AsyncTaskMethodBuilder оптимизирован для async-методов и ради повышения производительности является структурой, а не классом.
  • TaskAwaiter(<>u_awaiter) — здесь хранится временный объект, который ожидает завершения асинхронной задачи. Также представлен в виде структуры и помогает оператору await подписаться на уведомление о завершении задачи Task.

Для более детального изучения того, что же действительно происходит под капотом у компилятора рассмотрим IL кода, сформированного компилятором для d__0:
IL-код
.class nested private auto ansi sealed beforefieldinit '<MethodTaskAsync>d__0'
        extends [mscorlib]System.Object
        implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine
{
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
                01 00 00 00
        )
        // Fields
        .field public int32 '<>1__state'
        .field public valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder'
        .field public class Asynchronous.Program '<>4__this'
        .field private int32 '<one>5__1'
        .field private valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter '<>u__1'

        // Methods
        .method public hidebysig specialname rtspecialname 
                instance void .ctor () cil managed 
        {
                // Method begins at RVA 0x20ef
                // Code size 8 (0x8)
                .maxstack 8

                IL_0000: ldarg.0
                IL_0001: call instance void [mscorlib]System.Object::.ctor()
                IL_0006: nop
                IL_0007: ret
        } // end of method '<MethodTaskAsync>d__0'::.ctor

        .method private final hidebysig newslot virtual 
                instance void MoveNext () cil managed 
        {
                .override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
                // Method begins at RVA 0x20f8
                // Code size 185 (0xb9)
                .maxstack 3
                .locals init (
                        [0] int32,
                        [1] int32,
                        [2] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter,
                        [3] class Asynchronous.Program/'<MethodTaskAsync>d__0',
                        [4] class [mscorlib]System.Exception
                )

                IL_0000: ldarg.0
                IL_0001: ldfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                IL_0006: stloc.0
                .try
                {
                        IL_0007: ldloc.0
                        IL_0008: brfalse.s IL_000c

                        IL_000a: br.s IL_000e

                        IL_000c: br.s IL_0054

                        IL_000e: nop
                        IL_000f: ldarg.0
                        IL_0010: ldc.i4.s 33
                        IL_0012: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<one>5__1'
                        IL_0017: ldc.i4 1000
                        IL_001c: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Delay(int32)
                        IL_0021: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
                        IL_0026: stloc.2
                        IL_0027: ldloca.s 2
                        IL_0029: call instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
                        IL_002e: brtrue.s IL_0070

                        IL_0030: ldarg.0
                        IL_0031: ldc.i4.0
                        IL_0032: dup
                        IL_0033: stloc.0
                        IL_0034: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                        IL_0039: ldarg.0
                        IL_003a: ldloc.2
                        IL_003b: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>u__1'
                        IL_0040: ldarg.0
                        IL_0041: stloc.3
                        IL_0042: ldarg.0
                        IL_0043: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>t__builder'
                        IL_0048: ldloca.s 2
                        IL_004a: ldloca.s 3
                        IL_004c: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, class Asynchronous.Program/'<MethodTaskAsync>d__0'>(!!0&, !!1&)
                        IL_0051: nop
                        IL_0052: leave.s IL_00b8

                        IL_0054: ldarg.0
                        IL_0055: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>u__1'
                        IL_005a: stloc.2
                        IL_005b: ldarg.0
                        IL_005c: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>u__1'
                        IL_0061: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
                        IL_0067: ldarg.0
                        IL_0068: ldc.i4.m1
                        IL_0069: dup
                        IL_006a: stloc.0
                        IL_006b: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'

                        IL_0070: ldloca.s 2
                        IL_0072: call instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
                        IL_0077: nop
                        IL_0078: ldloca.s 2
                        IL_007a: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
                        IL_0080: ldarg.0
                        IL_0081: ldfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<one>5__1'
                        IL_0086: stloc.1
                        IL_0087: leave.s IL_00a3
                } // end .try
                catch [mscorlib]System.Exception
                {
                        IL_0089: stloc.s 4
                        IL_008b: ldarg.0
                        IL_008c: ldc.i4.s -2
                        IL_008e: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                        IL_0093: ldarg.0
                        IL_0094: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>t__builder'
                        IL_0099: ldloc.s 4
                        IL_009b: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [mscorlib]System.Exception)
                        IL_00a0: nop
                        IL_00a1: leave.s IL_00b8
                } // end handler

                IL_00a3: ldarg.0
                IL_00a4: ldc.i4.s -2
                IL_00a6: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                IL_00ab: ldarg.0
                IL_00ac: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>t__builder'
                IL_00b1: ldloc.1
                IL_00b2: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
                IL_00b7: nop

                IL_00b8: ret
        } // end of method '<MethodTaskAsync>d__0'::MoveNext

        .method private final hidebysig newslot virtual 
                instance void SetStateMachine (
                        class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
                ) cil managed 
        {
                .custom instance void [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
                        01 00 00 00
                )
                .override method instance void [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine)
                // Method begins at RVA 0x21d0
                // Code size 1 (0x1)
                .maxstack 8

                IL_0000: ret
        } // end of method '<MethodTaskAsync>d__0'::SetStateMachine

} // end of class <MethodTaskAsync>d__0


Метод MoveNext


Класс, который был сформирован для типа MethodTask реализует интерфейс IAsyncStateMachine, который представляет конечные автоматы, созданные для асинхронных методов. Этот тип предназначен только для использования компилятором. Данный интерфейс содержит следующие члены: MoveNext и SetStateMachine. Метод MoveNext перемещает конечный автомат к следующему его состоянию. Этот метод содержит в себе оригинальный код и вызывается как при первом входе в метод, так и после await. Как считается, любой конечный автомат начинает свою работу в некотором начальном состоянии. Даже в случае простейшего async-метода код MoveNext на удивление сложен, поэтому я попытаюсь описать его и представить как можно точнее в эквиваленте на C#.

Метод MoveNext назван так из-за сходства с методами MoveNext, которые генерировались блоками итераторов в предыдущих версиях C#. Эти блоки позволяют реализовать интерфейс IEnumerable в одном методе с помощью ключевого слова yield return. Применяемый для этой цели конечный автомат во многом напоминает асинхронный автомат, только проще.

Рассмотрим промежуточный код и разберем, что же в нем происходит(хочу заметить, что ниже я решил полностью описать язык CIL для более полного рассмотрения того, что генерирует компилятор, а также описал все инструкции, так что кому не интересны технические подробности можете пропустить):

.locals init (
                        [0] int32,
                        [1] int32,
                        [2] valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter,
                        [3] class Asynchronous.Program/'<MethodTaskAsync>d__0',
                        [4] class [mscorlib]System.Exception
                )


                IL_0000: ldarg.0
                IL_0001: ldfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                IL_0006: stloc.0

  • ldarg.0 — загружает аргумент 0 в стек. Аргумент с индексом 0 загружается в стек вычислений (промежуточный язык .NET является стековым), скопированный из входящего аргумента. Но у нас в методе нет аргументов! Дело в том, что по умолчанию в нестатических методах аргументом с индексом 0 всегда является указатель на экземпляр класса — this. Если же у вас при этом есть аргументы, то они будут уже иметь индекс 1, 2 и т.д. В статических метода ваши аргументы будут начинать отсчет с 0.
  • ldfld — выполняет поиск значения поля в объекте, ссылка на который находится в стеке. А сама ссылка была загружена выше при помощи ldarg.0, при этом значение, которое хранилось в этом поле соответственно загрузилось в стек.
  • stloc.0 — извлекает верхнее значение в стеке (это есть значение поля объекта MethodTaskAsync.state) и сохраняет его в списке локальных переменных с индексом 0. А список локальных переменных был еще объявлен в localsinit. Удобно, не правда ли?
                        IL_0007: ldloc.0
                        IL_0008: brfalse.s IL_000c

                        IL_000a: br.s IL_000e

                        IL_000c: br.s IL_0054

                        IL_000e: nop
                        IL_000f: ldarg.0
                        IL_0010: ldc.i4.s 33
                        IL_0012: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<one>5__1'
                        IL_0017: ldc.i4 1000
                        IL_001c: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Delay(int32)
                        IL_0021: callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter()
                        IL_0026: stloc.2
                        IL_0027: ldloca.s 2
                        IL_0029: call instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted()
                        IL_002e: brtrue.s IL_0070


  • ldloc.0 и brfalse.s — загружает локальную переменную с индексом 0 в стек вычислений. Здесь, только что сохраненное значение state загружается в стек, и команда brfalse.s передает управление конечной инструкции, если значением state является false, т.е. 0. При первом входе в метод, значение равно -1, значит выполнение потока инструкций идет дальше.
  • br.s IL_000e — безусловная передача конечной инструкции. Здесь выполнятся переход в другую часть кода, которую необходимо выполнить. В данном случае выполнение следующей команды будет происходить на строке IL_000e.
  • br.s IL_0054 — также безусловный переход, только эта команда выполнится, если выполнится команда brfalse.s
  • nop — Заполняет пространство, если коды операции содержат исправления. Никаких значимых операций не выполняется, хотя может быть пройден цикл обработки.
  • ldarg.0 и ldc.i4.s 33 — здесь происходит загрузка указателя this, а также загрузка числа 33 в стек, где ldc.i4.s — помещает значение с типом Int8 в стек вычислений как Int32 (краткая форма записи).
  • stfld — заменяет значение в поле объекта, по ссылке на объект на новое значение. С помощью загруженного указателя и числа 33 в стеке, в переменную член <one>5_1 (по умолчанию проинициализированной 0) загружается и сохраняется новое значение — 33. Как мы видим, это первая строка нашего исходного метода. Именно в этом блоке выполняется код оригинального метода.
  • ldc.i4 1000 — загрузка в стек переменной с типом Int32 как Int32.
  • call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Delay(int32) — здесь происходит вызов метода. Особенностью этой инструкции (по сравнению с инструкцией callvirt) является то, что адрес вызываемого метода вычисляется статически, то есть еще во время JIT-компиляции. В данном случае метод Delay является статическим. При этом параметры вызываемого метода должны быть расположены в стеке слева направо, то есть сначала на стек должен быть загружен первый параметр, затем второй и т.д.
  • callvirt instance valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter [mscorlib]System.Threading.Tasks.Task::GetAwaiter() — эта инструкция отличается от call главным образом тем, что адрес вызываемого метода определяется во время выполнения программы путем анализа типа объекта, для которого вызывается метод. Тем самым реализуется идея позднего связывания, необходимая для поддержки полиморфизма. При этом возвращаемое значение (в данном случае TaskAwaiter) помещается в стек, где TaskAwaiter представляет объект, который ожидает завершения асинхронной задачи.
  • stloc.2 — извлекает верхнее значение в стеке и сохраняет его в списке локальных переменных с индексом 2. При этом следует учесть, что верхнее значение в стеке есть результат выполнения операции GetAwaiter() и соответственно сохраняется это значение в локальной переменной с индексом 2
  • ldloca.s 2 — загружает локальное значение с индексом 2 в стек — недавно сохраненное значение
  • call instance bool [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::get_IsCompleted() — загрузка в стек значения, указывающее, было ли выполнение задачи закончено на момент обращения к свойству: true или false
  • brtrue.s IL_0070 — если задача выполнена, переходим к выполнению другого участка кода, если нет, идем далее.

Таким образом можно представить код, аналогичный следующему:
public void MoveNext()
{
   switch(this.1_state)
   {
      case -1:
         this.one = 33;
         var task = Task.Delay(1000);
         var awaiter = task.GetAwaiter(); // стоит отметить, что это локальные переменные метода, а не самого типа (исходя из IL-кода)
   
         if(!awaiter.IsCompleted)
         {
            ...
            return;
         }
   }
   ...
      //рассмотрим далее
}


Код, представленный выше, отвечает за начальное состояние конечного автомата и проверяет завершенность асинхронной задачи и переходит в нужное место метода. При этом происходит переход в одно из нескольких состояний автомата: приостановка метода в месте встречи await или синхронное завершение.

Приостановка метода


Рассмотрим IL-код в месте приостановки метода:
                        IL_0030: ldarg.0
                        IL_0031: ldc.i4.0
                        IL_0032: dup
                        IL_0033: stloc.0
                        IL_0034: stfld int32 Asynchronous.Program/'<MethosTaskAsync>d__0'::'<>1__state'
                        IL_0039: ldarg.0
                        IL_003a: ldloc.2
                        IL_003b: stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethosTaskAsync>d__0'::'<>u__1'
                        IL_0040: ldarg.0
                        IL_0041: stloc.3
                        IL_0042: ldarg.0
                        IL_0043: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Asynchronous.Program/'<MethosTaskAsync>d__0'::'<>t__builder'
                        IL_0048: ldloca.s 2
                        IL_004a: ldloca.s 3
                        IL_004c: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::AwaitUnsafeOnCompleted<valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, class Asynchronous.Program/'<MethosTaskAsync>d__0'>(!!0&, !!1&)
                        IL_0051: nop
                        IL_0052: leave.s IL_00b8


Описывать каждую операцию не стоит, так как все описано уже выше.
  • Данный кусок кода отвечает за изменение переменной state на 0, где команда stfld int32 Asynchronous.Program/'d__0'::'<>1__state' повторюсь означает изменение значения поля на новое значение. А чтобы возобновиться с нужного места, необходимо изменить переменную state.
  • Затем используется объект TaskAwaiter для подписки на уведомление о завершение задачи Task. Это происходит при загрузке в стек локальной переменной с индексом 2 и изменением значения поля на значение этой локальной переменной(команды ldloc.2 и stfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'d__0'::'<>u__1'). Затем возвращается управление и освобождается поток для других дел, как и полагается приличному асинхронному методу.
  • В процедуре подписки на уведомление участвует также объект AwaitUnsafeOnCompleted. Именно здесь реализуются дополнительные возможности await, в том числе запоминание контекста синхронизации, который нужно будет восстановить при возобновлении. Этот метод планирует конечный автомат для перехода к следующему действию по завершении выполнения указанного объекта типа awaiter. В качестве параметров: AsyncTaskMethodBuilder.AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine). Как видно перед вызовом этого метода происходит загрузка в стек двух переменных с индексом 2 и 3, где 2 — valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter, 3 — class Asynchronous.Program/'d__0'

Рассмотрим немного подробнее структуру AsyncTaskMethodBuilder(сильно глубоко копать здесь не буду, потому что на мой взгляд изучение этой структуры и все, что с ней связано можно расписать как бы не на несколько статей):
         /// <summary>Кэшированная задача для default(TResult).</summary>
        internal readonly static Task<TResult> s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult));
  
        /// <summary>Состояние, связанное с IAsyncStateMachine.</summary>
        private AsyncMethodBuilderCore m_coreState; // mutable struct: must not be readonly
        /// <summary>Ленивая инициализация задачи</summary>
        private Task<TResult> m_task; // lazily-initialized: must not be readonly

        /// <summary>
        /// Планирует состояние данной машины для дальнейшего действия, когда awaiter выполнится /// </summary>
        /// <typeparam name="TAwaiter">Определяет тип awaiter.</typeparam>
        /// <typeparam name="TStateMachine">Определяет тип состояния машины.</typeparam>
        /// <param name="awaiter">The awaiter.</param>
        /// <param name="stateMachine">Состояние машины.</param>
        [SecuritySafeCritical]
        public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
            ref TAwaiter awaiter, ref TStateMachine stateMachine)
            where TAwaiter : ICriticalNotifyCompletion
            where TStateMachine : IAsyncStateMachine
        {
            try
            {
                AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize = null;
                var continuation = m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn ? this.Task : null, ref runnerToInitialize);
                
                // Если это первый await, то мы не упаковали состояние машины и должны сделать это сейчас
                if (m_coreState.m_stateMachine == null)
                {
                    // Действие задачи должно быть проинициализировано до первого приостановления 
                    var builtTask = this.Task;
 
                    //Упаковка состояния машины при помощи вызова internal-метода,
                    // где ссылка будет храниться в кеше. 
                    m_coreState.PostBoxInitialization(stateMachine, runnerToInitialize, builtTask);
                }
 
                awaiter.UnsafeOnCompleted(continuation);
            }
            catch (Exception e)
            {
                AsyncMethodBuilderCore.ThrowAsync(e, targetContext: null);
            }
        }


Рассмотрим вкратце, что внутри этой структуры:
  • s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)) — здесь создается задача без реализации Dispose при указании специального флага DoNotDispose. Данный подход используется при создании задач для кеширования или повторного использования.
  • AsyncMethodBuilderCore m_coreState — представляет состояние, связанное с выполнением IAsyncStateMachine. Это структура.
  • AsyncMethodBuilderCore.MoveNextRunner runnerToInitialize — предоставляет возможность вызова метода конечного автомата MoveNext согласно предоставленному контексту выполнения программы. Это структура, которая содержит контекст выполнения, состояние конечного автомата и метод для выполнения MoveNext.
  • m_coreState.GetCompletionAction(AsyncCausalityTracer.LoggingOn? this.Task: null, ref runnerToInitialize) — получает Action при ожидании метода UnsafeOnCompleted и происходит прикрепление как задачи продолжения. Здесь также происходит запоминание состояния машины и контекста выполнения.
  • awaiter.UnsafeOnCompleted(continuation) — планирует продолжение действий, которые будут вызваны, когда экземпляр завершит выполнение. При этом в зависимости от того, нужно ли нам восстанавливать контекст или нет, будет вызван метод MoveNext соответственно с контекстом да приостановки метода или же выполнение продолжится в контексте того потока, в котором происходило выполнение задачи.

Получаем уже немного другой исходный код:
public void MoveNext()
{
   switch(this.1_state)
   {
      case -1:
         this.one = 33;
         var task = Task.Delay(1000);
         var awaiter = task.GetAwaiter(); // стоит отметить, что это локальные переменные метода, а не самого типа (исходя из IL-кода)
   
         if(!awaiter.IsCompleted)
         {
            this.1_state = 0;
            this.u__awaiter = awaiter; //u__awaiter это тип TaskAwaiter
            t_builder.AwaitUnsafeOnCompleted(ref this.u_awaiter, ref <MethodTaskAsync>d__0);
            return;
         }
   }
   ...
      //рассмотрим далее
}


Возобновление метода


После выполнения этого куска кода, вызывающий поток уходит заниматься своими делами, а мы пока тем временем ожидаем завершения задачи. После того, как задача завершилась, заново вызывается метод MoveNext(при этом вызов метода AwaitUnsafeOnCompleted сделал все необходимое для работы). Рассмотрим IL-код, который вызывается при продолжении:
                        IL_0054: ldarg.0
                        IL_0055: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>u__1'
                        IL_005a: stloc.2
                        IL_005b: ldarg.0
                        IL_005c: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.TaskAwaiter Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>u__1'
                        IL_0061: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
                        IL_0067: ldarg.0
                        IL_0068: ldc.i4.m1
                        IL_0069: dup
                        IL_006a: stloc.0
                        IL_006b: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'

                        IL_0070: ldloca.s 2
                        IL_0072: call instance void [mscorlib]System.Runtime.CompilerServices.TaskAwaiter::GetResult()
                        IL_0077: nop
                        IL_0078: ldloca.s 2
                        IL_007a: initobj [mscorlib]System.Runtime.CompilerServices.TaskAwaiter
                        IL_0080: ldarg.0
                        IL_0081: ldfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<one>5__1'
                        IL_0086: stloc.1
                        IL_0087: leave.s IL_00a3

                        IL_00a3: ldarg.0
                        IL_00a4: ldc.i4.s -2
                        IL_00a6: stfld int32 Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>1__state'
                        IL_00ab: ldarg.0
                        IL_00ac: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> Asynchronous.Program/'<MethodTaskAsync>d__0'::'<>t__builder'
                        IL_00b1: ldloc.1
                        IL_00b2: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
                        IL_00b7: nop


  • В первой части происходит загрузка аргумента с индексом 0 — это this, затем происходит поиск переменной TaskAwaiter <>u__1, сохранение ее значения в локальной переменной с индексом 2, а затем повторная инициализация. После этого происходит загрузка в стек значения -1 и сохранения этого значения в переменной 1__state.Таким образом выполняется сброс состояния задачи.
  • Во второй части происходит загрузка в стек локальной переменной awaiter и вызов GetResult(). Затем новая загрузка в стек локальной переменной и ее новая инициализация. Затем происходит загрузка в стек переменной 5__1 и сохранение ее в локальной переменной с индексом 1 и переход к другой команде.
  • В третий части загрузка в стек значения -2 и сохранение ее в переменной 1_state. Затем загрузка в стек переменной t_builder и вызов метода SetResult(one).

В результате приблизительный исходный код:
public void MoveNext()
{
   switch(this.1_state)
   {
      case -1:
         this.one = 33;
         var task = Task.Delay(1000);
         var awaiter = task.GetAwaiter(); // стоит отметить, что это локальные переменные метода, а не самого типа (исходя из IL-кода)
   
         if(!awaiter.IsCompleted)
         {
            this.1_state = 0;
            this.u__awaiter = awaiter; //u__awaiter это тип TaskAwaiter
            t_builder.AwaitUnsafeOnCompleted(ref this.u_awaiter, ref <MethodTaskAsync>d__0);
            return;
         }

       case 0:
          var awaiter = this.u_awaiter;
          this.u_awaiter = new System.Runtime.CompilerServices.TaskAwaiter();
          this.1_state = -1;
     
          awaiter.GetResult();
          awaiter = new System.Runtime.CompilerServices.TaskAwaiter();
          var one = this.<one>5_1;

          this.1_state = -2;
          this.t_builder.SetResult(one);
   }
}


Синхронное завершение


В случае синхронного завершения не следует останавливать и возобновлять метод. В таком случае просто необходимо проверить выполнение метода и перейти в нужное место при помощи оператора goto case:
public void MoveNext()
{
   switch(this.1_state)
   {
      case -1:
         this.one = 33;
         var task = Task.Delay(1000);
         var awaiter = task.GetAwaiter(); // стоит отметить, что это локальные переменные метода, а не самого типа (исходя из IL-кода)
   
         if(awaiter.IsCompleted)
         {
            goto case 0;
         }

       case 0:
          this.1_state = 0;
          ...
   }
}


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

И напоследок...


В данной статье я опирался на одну из моих любимых книг по асинхронному программированию в C# 5.0 Алекса Дэвиса. Вообще советую ее всем прочитать, поскольку она небольшая (при желании можно прочитать за один день) и очень интересно и в меру подробно описывает механизм async/await в целом. При этом можно почитать и новичкам, там все очень просто написано(примеры из жизни и тому подобное). При это читая ее и параллельно изучая IL-код, я нашел небольшое расхождение с тем, что пишется в книге и есть на самом деле. Но думаю, что скорее всего дело в том, что скорее всего с тех пор подправили немного компилятор и он стал выдавать немного другие результаты. Но это не столь критично, чтобы сильно на этом зацикливаться. При этом в качестве исходного кода (для описания AsyncTaskMethodBuilder использовал данный ресурс: это если кому будет интересно копнуть еще глубже).

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Патчим mysqldump в домашних условиях

image
Бекапы это хорошо, а еще лучше когда они работают так как надо когда они нужны. На одном из проектов понадобилось восстановить дамп из 745 триггеров и накатить их на рабочую MySQL базу.

MySQL позволяет использовать любые имена триггеров, в том числе с использованием точек (например: analitica.cron.indeg.y.run.a_insert). А mysqldump при создании дампа не учитывает это обстоятельство и добавляет конструкцию для их дропа следующего вида:

/*!50032 DROP TRIGGER IF EXISTS analitica.cron.indeg.y.run.a_insert */;

Подвох ожидает при попытке эти дампы накатить на базу где эти триггеры уже созданы. С точки зрения MySQL этот запрос не дропнет триггер, потому что не найдет триггер с таким именем. Для корректной работы имя триггера должно быть заключено в апострофы.
В попытках найти обходное решение зарепортил соответствующий репорт в баг трекер Перконы, а они уже продублировали аналогичный репорт в официальный трекер MySQL.

Поскольку исправлять этот баг будут долго, а дампы мне нужны прямой сейчас. Решить эту задачу я решил самым прямолинейным способом, а именно самостоятельно пропатчив mysqldump. Для этого склонировал официальный репозиторий перконовского дитрибутива MySQL 5.6 с GitHub'а.

git clone --recursive --depth 1 http://ift.tt/1JXlhxv

Открыл файл client/mysqldump.c и добавил апострофы к конструкции DROP TRIGGER IF EXISTS в паре мест. Если посмотреть diff, то получается такой патч:
@@ -3517,7 +3517,7 @@ static void dump_trigger_old(FILE *sql_file, MYSQL_RES *show_triggers_rs,
     fprintf(sql_file, "/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;\n");

   if (opt_drop_trigger)
-    fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", (*show_trigger_row)[0]);
+    fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS `%s` */;\n", (*show_trigger_row)[0]);

   fprintf(sql_file,
           "DELIMITER ;;\n"
@@ -3604,7 +3604,7 @@ static int dump_trigger(FILE *sql_file, MYSQL_RES *show_create_trigger_rs,
     switch_sql_mode(sql_file, ";", row[1]);

     if (opt_drop_trigger)
-      fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", row[0]);
+      fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS `%s` */;\n", row[0]);

Чтобы собрать пропатченую версию надо поставить пару пакетов и на Ubuntu/Debian, запустить cmake с параметрами из документации к Перконе, а затем make'ом собрать только mysqldump.
apt-get install build-essential cmake bison libaio-dev libncurses5-dev libreadline-dev
cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_CONFIG=mysql_release -DFEATURE_SET=community -DWITH_EMBEDDED_SERVER=OFF
make mysqldump

После компиляции получаем патченую версию mysqldump, в которой имена триггеров экранируются корректно. Можно дампить новым дампером:
./percona-server/client/mysqldump \
    --socket=/var/run/mysqld/mysqld.sock \
    -uroot -p --routines --events --triggers \
    --add-drop-trigger --quote-names \
    --no-create-info --no-data --no-create-db --skip-opt \
    database_name | sed -r 's/DEFINER[ ]*=[ ]*[^*]*\*/\*/' > dump.sql

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Потенциально до 1 миллиона «живых» аккаунтов VK.com оказалось скомпрометировано злоумышленниками

image«Лаборатория Касперского» заявила, что обнаружила масштабную аферу по хищению учетных данных пользователей «ВКонтакте» через приложение для проигрывания музыки. Жертвами хакеров, по данным компании, могли стать сотни тысяч человек, говорится в сообщении на сайте компании.

Для хищения информации использовалось приложение для прослушивания музыки под названием «Музыка ВКонтакте». Жертвы скачивали его из официального магазина Google Play, где собрано программное обеспечение (ПО) для Android-устройств. По грубым оценкам «Лаборатории Касперского», число пострадавших может исчисляться сотнями тысяч.

Кража данных происходила после того, как пользователь авторизовался в приложении, то есть вводил свои логин и пароль, установленные для доступа в свой аккаунт «ВКонтакте». Примечательно, что злоумышленники проверяли подлинность этих данных, отправляя их на легитимный сервер аутентификации oauth.vk.com, отмечают в «Касперском», а пользователи не догадывались о вредоносности программы, поскольку она справлялась со своей заявленной функцией — проигрывала аудиозаписи из «ВКонтакте».
Впоследствии злоумышленники чаще всего использовали украденные сведения для добавления аккаунтов пользователей в различные сообщества, которые собирались «раскручивать» в социальной сети. Однако в ряде случаев похитители просто меняли пароль, присваивая себе учетную запись.

Пресс-секретарь соцсети Георгий Лобушкин заявил, что пользователи «ВКонтакте», столкнувшиеся с хищением информации через приложение для проигрывания музыки, фактически добровольно отдали мошенникам свои данные.

Пользователям ВКонтакте, использовавшим сторонние приложения, рекомендуется в срочном порядке сменить пароли и активировать двухфакторную аутентификацию.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 9 октября 2015 г.

Утечка реальных ФИО пользователей через Кинопоиск.ру

8 октября 2015 года Яндекс «перезапустил» купленный два года назад сайт Кинопоиск.ру. Судя по всему, это крупная неудача Яндекса. Число критических сообщений от пользователей старого Кинопоиска растёт как лавина.

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

Но хуже то, что теперь необходимо привязывать аккаунты бывшего Кинопоиска к аккаунтам Яндекса. На открытых персональных страницах пользователей выводятся имена аккаунтов Яндекса, а над ними — вот те привет! — ФИО, указанные в «паспортах» Яндекса, огромная масса которых является вполне себе не вымышленными, конфиденциальными данными.

Разумеется, согласия на вывод реальных ФИО в общий доступ никто не спрашивает ни со стороны Кинопоиска, ни со стороны Яндекса.

Теперь перебором по ID пользователей Кинопоиска можно собрать базу данных, состоящую из реальных ФИО пользователей и имен аккаунтов Яндекса, они же почта, Яндекс.Деньги и прочие сервисы.

Через Гугл ищутся и деанонимируются ники, вроде Mickey Freak, или наоборот по реальным ФИО находится связка с ником вроде Mickey Freak:

Сообщение в службу поддержки Кинопоиска отправлено несколько часов назад.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] А ваш язык программирования необоснованный? (или почему предсказуемость важна)

Как должно быть очевидно, одна из целей этого сайта — убедить принимать F# всерьёз в роли универсального языка разработки.

Но в то время как функциональный стиль всё больше проникает в массы, и C# уже получил такие функциональные средства как лямбды и LINQ, кажется, что C# всё больше и больше наступает на пятки F#. Так что, как это ни странно, но я стал всё чаще слышать как высказывают такие мысли:

  • «C# уже обладает большей частью инструментария F#, и зачем мне напрягаться с переходом?»
  • «Нет никакой необходимости что-то менять. Всё, что нам нужно сделать, так это пару лет подождать, и C# получит достаточно от F#, что обеспечит практически все плюшки.»
  • «F# только чуть лучше, чем C#, но не настолько, чтобы в самом деле тратить время с переходом на него.»
  • «F# кажется действительно неплох, хоть и пугает местами. Но я не могу найти ему практического применения, чтобы использовать вместо C#.»

Не сомневаюсь, что теперь, когда и в Java тоже добавлены лямбды, подобные комментарии зазвучали в экосистеме JVM при обсуждении «Scala и Closure против Java».

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

Но C# не идеален. Как и в большинстве основных объектно-ориентированных языков, в нём присутствуют проектные решения, которые не могут быть компенсированы никакими прелестями LINQ или лямбд. В этой статье я покажу вам некоторые проблемы, которые вызваны такими решениями, и предложу способы улучшения языка, которые помогут избежать этих проблем.

(Теперь я надеваю свой огнеупорный костюм. Думаю, он мне понадобится!)

ДОПОЛНЕНИЕ: похоже, что многие читатели совсем не поняли эту статью. Позвольте я проясню:

  • я не говорю, что языки со статической типизацией «лучше», чем с динамической.
  • я не говорю, что функциональные языки «лучше», чем объектно-ориентированные.
  • я не говорю, что возможность продумывать код является наиболее важным аспектом языка.

Я говорю, что:

— невозможность продумывать код приносит издержки, в которых многие разработчики не отдавали себе отчёта.
— более того, возможность «продумывания» должна быть только одним (из многих) фактором оценки, когда выбирается язык программирования, просто о нём не стоит забывать из-за недостатка предостережений.
— ЕСЛИ вы хотите иметь возможность продумывать свой код, ТО наличие в вашем языке свойств, о которых я расскажу, сделает это намного проще.
— фундаментальные концепции объектно-ориентированного программирования (уникальность объектов, ориентированность на поведение) не совместимы с «продумыванием», и поэтому будет сложно доработать существующие ОО языки, чтобы добавить эту возможность.

Больше ничего. Спасибо!



Всё-таки, что же такое «обоснованный» язык программирования?


Если вы знакомы с функциональными программистами, то часто слышите словосочетание «продумывать что-то», например «мы хотим продумать наши программы».

Что это значит? Зачем использовать слово «продумывать» вместо обычного «понять»? Использование «продумывания» берёт начало в математике и логике, но я собираюсь использовать простое и практичное определение:

  • «продумывание кода» означает что вы можете делать умозаключения, используя только то, что вы видите здесь и сейчас, без раскопок в других блоках программного кода.

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

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

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

Пример 1


Давайте приступим к рассмотрению следующего кода.
  • Мы начинаем с переменной x, которой присваивается целочисленное значение 2.
  • После этого вызывается DoSomething, которой x передаётся как параметр.
  • И после этого переменной y присваивается результат выражения x — 1.

Вопрос, который я задам, не сложен: каково значение y?
var x = 2;
DoSomething(x);

// Каково значение y? 
var y = x - 1;


Ответ
Ответом будет -1. Вы получили такой результат? Нет? Если вы не можете понять это, смотрите далее.
Расшифровка
Шуточный вопрос! Этот код написан на JavaScript! Вот как он выглядит полностью:
function DoSomething (foo) { x = false}

var x = 2;
DoSomething(x);
var y = x - 1;


Да, это ужасно! DoSomething получает доступ к x напрямую, а не через параметр, и (вот тебе раз!) превращает его в переменную с булевым типом. После этого вычитание единицы превращает x из false в 0 (с преобразованием типа), и y в результате получается -1.
Вы полностью взбешены? Простите, что сбил с толку вас подменой языка, но я хотел просто продемонстрировать как это бесит, когда язык ведёт себя непредсказуемым образом.

JavaScript — очень удобный и важный язык. Но никто не может утверждать, что логичность является его сильной стороной. В действительности, в большинстве языков с динамической типизацией присутствуют странности, которые создают трудности для продумывания кода.

Благодаря статической типизации и разумным правилам определения области видимости ошибки такого рода не могут возникать в коде на C# (только, конечно, если не очень постараетесь). В C# если правильно не подобрать типы, возникает ошибка времени компиляции, а не времени выполнения.

Другими словами, C# намного более предсказуем, чем JavaScript. Первый балл за статическую типизацию! Итак мы получили первую рекомендацию для создания предсказуемого языка.

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.

C# выглядит неплохо по сравнению с JavaScript. Но мы ещё не закончили…

ДОПОЛНЕНИЕ: Это был заведомо глупый пример. Если бы мог вернуться в прошлое, я бы выбрал пример получше. Да, я знаю, что ни один разумный человек не будет делать так. Но суть дела не меняется: JavaScript не запрещает вам делать глупости с неявным приведением типов.

Пример 2


В следующем примере мы собираемся создать два экземпляра класса Customer с одинаковыми данными в них.
Вопрос: они равны?
 // создаём двух заказчиков
var cust1 = new Customer(99, "J Smith");
var cust2 = new Customer(99, "J Smith");

// истина или ложь?
cust1.Equals(cust2);

Ответ
Кто знает. Это зависит от того, как Customer реализован. Этот код не предсказуем.
Вы должны, по крайней мере, узнать реализован ли в классе IEquatable, и вероятно вы будете должны посмотреть код реализации класса, чтобы понять что конкретно там происходит.

Но почему этот вопрос вообще возникает? Позвольте мне у вас спросить:

— Как часто вы НЕ хотите, чтобы экземпляры были приравнены?
— Как часто вам приходилось переписывать метод Equals?
— Как часто вы сталкивались с ошибкой из-за того, что забыли переопределить метод Equals?
— Как часто вы сталкивались с ошибкой, вызванной неправильной реализацией метода GetHashCode (например, забыли изменить его результат при изменении полей, по которым проводится сравнение)?

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

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.

2. Объекты с идентичным содержанием должны быть равными по умолчанию.


Пример 3


В следующем примере у меня два объекта содержат идентичные данные, но являются экземплярами разных классов. Вопрос тот же: они равны?
// создаём заказчика и счёт
var cust = new Customer(99, "J Smith");
var order = new Order(99, "J Smith");

// истина или ложь?
cust.Equals(order);


Ответ
Ну кого это может волновать! Это определённо ошибка! Во-первых, зачем вообще сравнивать объекты двух различных классов? Сравнивайте их имена или идентификаторы, только не сами объекты. Здесь должна возникать ошибка при компиляции. Но если это не так, то почему бы и нет? Вероятно вы только что ошибочно использовали имя не той переменной, и теперь получили в своём коде ошибку, которую нелегко отловить. Почему ваш язык позволил вам это?

Добавим ещё один пункт в наш лист.

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.
2. Объекты с идентичным содержанием должны быть равными по умолчанию.
3. Сравнение объектов с разными типами должно вызывать ошибку времени компиляции.

ДОПОЛНЕНИЕ: многие утверждают, что это необходимо для сравнения классов, находящихся в отношении наследования. Конечно, это верно. Но какова цена этой возможности? Вы получаете возможность сравнивать дочерние классы, но теряете способность обнаруживать случайные ошибки. Что в реальной работе более важно? Это решать вам, я только хотел явно показать что у принятой практики есть и недостатки, а не только преимущества.


Пример 4


В этом фрагменте кода мы просто создадим экземпляр класса Customer. И всё. Не могу придумать что-то более простое.
// создаём заказчика
var cust = new Customer();

// что ожидается на выходе?
Console.WriteLine(cust.Address.Country);


Теперь вопрос в следующем: какой вывод мы ожидаем от WriteLine?
Ответ
Да кто ж его знает. Это зависит будет ли свойство Address равно null или нет. И это что-то, что опять невозможно определить без того, чтобы заглянуть во внутренности класса Customer.

Да, мы знаем, что идеальный вариант, когда конструкторы инициализируют все поля во время создания объекта, но почему язык не завставляет это делать? Когда наличие адреса необходимо, сделайте это поле обязательным в конструкторе. А если адрес требуется не всегда, укажите явно, что это свойство не обязательно и может быть опущено.

Ещё один пункт в наш список.

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.
2. Объекты с идентичным содержанием должны быть равными по умолчанию.
3. Сравнение объектов с разными типами должно вызывать ошибку времени компиляции.
4. Объекты всегда должны быть инициализированными до корректного состояния. Невыполнение этого требования должно приводить к ошибке времени компиляции.

Пример 5


В следующем примере мы проделаем следующее:
— создадим заказчика
— добавим его в хеш-множество
— сделаем что-нибудь с объектом заказчика
— попробуем найти заказчика в множестве

Что может пойти не так?

// создаём заказчика
var cust = new Customer(99, "J Smith");

// добавляем его в множество
var processedCustomers = new HashSet<Customer>();
processedCustomers.Add(cust);

// обрабатываем его
ProcessCustomer(cust);

// Он всё ещё в множестве? истина или ложь?
processedCustomers.Contains(cust);


Итак, содержит ли множество объект заказчика после выполнения этого кода?
Ответ
Может быть да. А может и нет.
Это зависит от двух моментов:
— во-первых, зависит ли хеш-код от модифицируемого поля, например, такого как идентификатор?
— во-вторых, меняет ли ProcessCustomer это поле?

Если на оба вопроса ответ утвердительный, хеш будет изменён, и объект заказчика не будет больше виден в этом множестве (хотя он и присутствует где-то там внутри!). Это может привести к падению производительности и проблемам с памятью (например, если множество используется для реализации кеша). Как язык может предотвратить это?

Один способ — сделать немодифицируемыми поля, используемые в GetHashCode, и оставить модифицируемыми все остальные. Но это очень неудобно.

Вместо этого лучше сделать немодифицируемым весь класс Customer! Теперь, если объект Customer неизменяемый, и ProcessCustomer захочет сделать изменения, он будет обязан вернуть новую версию объекта заказчика, а код будет выглядеть например так:

// создаём заказчика
var cust = new Customer(99, "J Smith");

// добавляем его в множество
var processedCustomers = new HashSet<Customer>();
processedCustomers.Add(cust);

// обрабатываем его и возвращаем изменения
var changedCustomer = ProcessCustomer(cust);

// истина или ложь?
processedCustomers.Contains(cust);


Заметьте, что вызов ProcessCustomer изменён на
var changedCustomer = ProcessCustomer(cust);


Теперь ясно, что ProcessCustomer что-то изменяет, если просто посмотреть на эту строку. Если ProcessCustomer ничего не изменяет, то объект и вообще незачем возвращать.

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

Разумеется, это не даёт ответа на вопрос должен ли в множестве присутствовать новый объект, или старый объект (или оба вместе). Но в сравнении с версией с модифицируемым объектом заказчика, описанная проблема теперь упирается вам прямо в лоб и не может быть случайно упущена. Так что немодифицируемость на коне!

Это ещё один пункт в нашем списке.

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.
2. Объекты с идентичным содержанием должны быть равными по умолчанию.
3. Сравнение объектов с разными типами должно вызывать ошибку времени компиляции.
4. Объекты должны всегда быть инициализированными в корректное состояние. Невыполнение этого требования должно приводить к ошибке времени компиляции.
5. После создания объекты и коллекции должны оставаться неизменными.

Момент как раз для шутки о немодифицируемости:

— Сколько программистов на Хаскелл нужно, чтобы поменять лампочку?
— Программисты на Хаскелл не меняют лампочки, они их заменяют. И вы должны одновременно заменить весь дом.

Почти закончили — остался один!


Пример 6


В этом заключительном примере мы попробуем получить заказчика из CustomerRepository.
// создаём репозиторий
var repo = new CustomerRepository();

// ищем заказчика по идентификатору
var customer = repo.GetById(42);

// что мы ожидаем на выходе?
Console.WriteLine(customer.Id);


Вопрос: после того как мы выполнили:
var customer = repo.GetById(42)


, каково значение customer.Id?
Ответ
Конечно, это всё неопределённо.

Если я посмотрю на сигнатуру метода GetById, она скажет мне, что он всегда возвращает объект Customer. Но так ли это?

Что происходит, когда заказчик не найден? repo.GetById вернёт null? Или выбросит исключение? Вы не сможете это определить, просто глядя на этот фрагмент.

Что касается null, это ужасный вариант для возвращаемого значения. Это оборотень, который прикидывается заказчиком и может быть присвоен переменным типа Customer совершенно без звука со стороны компилятора, но когда вы попросите его что-нибудь сделать, он взорвётся вам в лицо злобным хохотом. Как ни печально, но я не могу определить вернётся или не вернётся из этого метода null.
Исключения только немногим лучше, потому что, по крайней мере, они типизированы и содержат информацию о контексте. Но по сигнатуре метода совершенно невозможно понять какие исключения могут быть выброшены. Единственный способ удостовериться — заглянуть внутрь исходного кода метода (или в документацию, если вам повезло, и она обновляется).

А теперь представьте, что ваш язык не разрешает использовать null и не позволяет выбрасывать исключения. Что бы вы тогда могли сделать?
Чтобы ответить на это, вам пришлось бы возвращать специальный класс, который способен содержать в себе или объект заказчика, или ошибку, например, так:

// создаём репозиторий
var repo = new CustomerRepository();

// ищем заказчика по идентификатору
// и возвращаем результатом объект типа CustomerOrError
var customerOrError = repo.GetById(42);


Код, который будет обрабатывать результат customerOrError, должен проверить какого вида ответ и обработать по отдельности каждый вариант:
// обработать оба случая
if (customerOrError.IsCustomer)
    Console.WriteLine(customerOrError.Customer.Id);

if (customerOrError.IsError)
    Console.WriteLine(customerOrError.ErrorMessage);


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

И теперь, наконец, два последних пункта в списке.

Как сделать язык предсказуемым:

1. Переменным не разрешается менять тип.
2. Объекты с идентичным содержанием должны быть равными по умолчанию.
3. Сравнение объектов с разными типами должно вызывать ошибку времени компиляции.
4. Объекты должны всегда быть инициализированными в корректное состояние. Невыполнение этого требования должно приводить к ошибке времени компиляции.
5. После создания объекты и коллекции должны оставаться неизменными.
6. Запретить использование null.
7. Отсутствие данных или возможность ошибки должны быть представлены явно в сигнатуре метода.

Я мог быть продолжать с примерами, демонстрирующими неправильное использование глобальных переменных, побочные эффекты, приведение типов и т.д. Но думаю, что стоит здесь остановиться — вероятно вы уже догадались!


Может ваш язык программирования делать это?


Надеюсь, очевидно, что такие дополнения помогают сделать язык более обдумываемым. К несчастью, основные ОО языки типа C#, скорее всего, не получат этих приобретений.

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

Кроме того, с точки зрения ОО, способ, которым должны сравниваться два объекта, зависит полностью от их реализации — ОО полностью посвящено полиморфизму поведения, так что компилятор здесь не должен лезть не в своё дело! Это же касается и порядка создания, и порядка инициализации объектов, которые полностью определяются в них самих. Здесь не существует законов, которые говорят разрешать это или не разрешать.

В конечном итоге, очень сложно добавить ссылочные типы с запретом нулевых ссылок в статически типизируемый ОО язык без реализации ограничений инициализации по четвёртому пункту. Как сказал Эрик Липперт "Запрет null-ссылок — это штука, которую легко предусмотреть в системе типов в день первый, но не то, что вы захотите доработать спустя двенадцать лет."

Напротив, в большинстве функциональных языков эти «предсказательные» инструменты реализованы в ядре языка.

Например, в F# все пункты списка, кроме одного, встроены в язык:
1. Значения не могут менять свой тип. (И это включает даже невозможность неявного приведения целого числа к плавающему).
2. Переменные типа запись с идентичным внутренним наполнением РАВНЫ по умолчанию.
3. Сравнение значений с разными типами ВЫЗЫВАЕТ ошибку времени компиляции.
4. Значения ДОЛЖНЫ быть инициализированы до корректного состояния. Невыполнение этого требования приводит к ошибке времени компиляции.
5. После создания объекты НЕ модифицируемы по умолчанию.
6. Значения null НЕ разрешены, в большинстве случаев.

Пункт 7 не проверяется компилятором, но для возврата ошибок, как правило, используются не исключения, а типы дизъюнктивного объединения (сум-типы), так что сигнатура функции чётко показывает какие возможные ошибки она может возвращать.

Конечно, при работе с F# у вас всё-таки остаётся множество проблемных моментов. Вы можете получить модифицируемые значения, вы можете создавать и бросать исключения, и вы можете на самом деле столкнуться с null, которые передаются из не-F# кода. Но эти проблемы будут возникать в коде как дурно пахнущие кучки, потому что не являются стандартными и не используются в обычной практике.

Другие языки, например, Хаскелл, являются даже более пуританскими, чем F#, а значит даже лучше поддерживают продумывание, но даже на Хаскелле программы не могут быть идеальными. В самом деле, ни один язык не может быть идеально предсказуемым и, в то же время, оставаться практичным. И всё-таки некоторые языки более предсказуемы, чем другие.

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

Лямбды — это не выход


Итак, теперь должно быть ясно, что этот список предложенных улучшений не имеет ничего общего с такими усовершенствованиями языка, как лямбды или умные функциональные библиотеки. Другими словами, когда я рассматриваю предсказуемость языка — я не беспокоюсь по поводу что мой язык позволяет мне делать, я переживаю по поводу чего он сделать мне не позволит. Я хочу язык, который не даст мне делать глупости нечаянно.

То есть, если я должен выбирать между языком А, который не разрешает nullы, и языком B, который поддерживает параметрический полиморфизм высшего порядка, но разрешает объектам быть nullами, я без колебаний выбираю А.

Комментарии


Дайте-ка я попробую предугадать несколько комментариев…

Комментарий: Эти примеры высосаны из пальца! Если писать аккуратно и следовать правильным методикам, можно писать безопасный код без этих фенечек!

Да, можно. Я не утверждаю, что вы не можете. Но эта статья не о написании безопасного кода, она об обдумывании кода. В этом есть разница.
И она не о том, что вы сможете сделать, если будете аккуратны. Она о том, что может случиться, если вы не будете аккуратны! То есть, помогает ли ваш язык программирования (а не стандарты кодирования, не тесты, не IDE, не методики разработки) размышлять над вашим кодом?

Комментарий: Вы говорите, что язык иметь эти способности обязан. Не очень ли бесцеремонно это с вашей стороны?

Пожалуйста, читайте внимательно. Я вообще не говорю этого. Вот о чём я говорю: ЕСЛИ вы хотите иметь возможность размышлять над кодом, ТОГДА будет значительно легче это делать с языком, который содержит возможности, о которых я упоминал. Если размышление над кодом не имеет значения для вас, пожалуйста, со спокойной душой проигнорируйте всё, что я сказал!

Комментарий: концентрация только на одном аспекте языка программирования слишком ограничивает. Разве другие качества не являются столь же важными?

Да, конечно, являются. Я не деспот в этом вопросе. Я считаю, что такие свойства, как комплексные исчерпывающие библиотеки, хорошие инструменты разработки, дружественное сообщество и жизнеспособность экосистемы также очень важны. Но целью этой статьи было ответить на конкретные вопросы, которые я упоминал в начале, например, «В C# уже присутствует большинство средств F#, зачем беспокоиться и переходить?»

Комментарий: Почему вы так быстро списали динамические языки?
Во-первых, приношу свои извинения разработчикам на JavaScript за шпильку в их адрес! Мне очень нравятся динамические языки, а один из моих любимых языков, Smalltalk, совершенно не обеспечивает размышление над кодом в соответствии с принципами, о которых я говорил. К счастью, эта статья не попытка уговорить вас какие языки считать вообще «лучшими», а всего лишь обсуждение одного из аспектов выбора языка.

Комментарий: немодифицируемые структуры данных медленны, и с ними выполняется громадное количество дополнительных распределений памяти. Не пострадает ли производительность?

Эта статья не пытается оценивать влияние данных свойств языка на производительность (или что-то другое). Но возникает справедливый вопрос — что должно иметь более высокий приоритет: качество кода или производительность? Вам решать, и это зависит от задачи. Лично я в первую очередь смотрю на безопасность и качество, если только нет настоятельного требования не делать так. Вот знак, который мне нравится:

Выводы


Я только что сказал, что эта статья не является попыткой склонить вас к выбору языка с поддержкой «продумывания кода». Но это не совсем правда.
Если вы уже выбрали статически типизированный, высокоуровневый язык (как C# или Java), становится ясно, что «продумывание кода» или что-то наподобие этого было важным критерием при принятии решения.

В таком случае я надеюсь, что примеры в этой статье могут заставить вас подумать об использовании более «рассуждаемого» языка на платформе, которую вы выбрали (.NET или JVM).

Аргумент оставаться со своим языком (- что ваш язык в конечном итоге «догонит») может срабатывать в части свойств языка, но никакое количество будущих улучшений не изменит по-настоящему основную составляющую принципов проектирования в ОО языке. Вы никогда не избавитесь от null, от модифицируемости объектов, или требования каждый раз переопределять методы определения их равенства.

И вот что замечательно в F# или Scala/Clojure — эти альтернативные функциональные языки не заставят вас переходить в другую экосистему, но в то же время мгновенно улучшат качество вашего кода.

Мне кажется, что это сравнительно небольшой риск по отношению к затратам.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Мы делили интернет: много нас, а его нет

Сегодня мы расскажем о ситуациях, когда одной настройкой QoS для роутера не обойдешься. Представить себе туриста, отправившегося в путешествие хотя бы без мобильного телефона, сегодня практически невозможно. Но цены и наших, и зарубежных операторов связи в роуминге по-прежнему весьма высоки. И если поговорить несколько минут c родными и друзьями или отправить СМС не так и дорого, то доступ в Сеть может серьёзно ударить по карману. Но ведь во всех отелях есть бесплатный WiFi, правда?
image

«Всё хорошо, но в номере поймать WiFi практически невозможно – только на ресепшене», «Интернет тут совсем никакой», «WiFi постоянно обрывался»… Всё это – совершенно реальные отзывы о самых разных отелях, возможно даже, ваши. И как результат – жирный минус в оценках, даже если все остальное вполне устраивает. Почему?
Разберёмся, что влияет на скорость и качество WiFi-соединения. Мы не будем говорить о таких очевидных факторах, как удалённость от точки доступа или маршрутизатора беспроводной сети от устройства пользователя.

Представим: в определённый момент времени к сети подключено десяток гостей нашего отеля и компьютер на ресепшн. Сразу оговоримся: мы исходим из того, что в сети отсутствует классификация трафика, разделение его по приоритетам, и весь трафик распределяется поровну среди всех пользователей. Почему админ не подумал об этом – неважно. Но это не такая уж редкость, как может показаться.

Обычный интернет-сёрфинг и обмен текстовыми сообщениями практически не влияют на скорость для конечных пользователей. Пока канал загружен меньше, чем на 70%, никаких проблем нет, скорость стабильная.

Но разве могут гости отказаться от того, чтобы выложить свежие отпускные фотографии на свою страничку в социальной сети? Будучи за границей, где российское телевидение не всегда доступно, пропустить очередную серию любимого сериала? Позвонить по Skype, к которому все так привыкли? Посмотреть видео в HD-качестве на YouTube? Два-три таких пользователя – и скорость у остальных снижается более чем заметно. А вот в сети появился любитель торрентов. У него всё отлично: ещё несколько минут – и фильм закачан. Все остальные – не пройдёт и минуты – останутся практически без интернета.

Для того, чтобы избежать таких малоприятных ситуаций, и существует технология QoS  (Quality of Service). Важно понять: пока настройки сервиса QoS  отключены и не регулируют пропускную способность сети, она контролируется со стороны оборудования поставщика услуг. Которому, по большому счёту, наши проблемы не очень важны: скорость соответствует тарифу? Да? Дальше думайте сами.

Активировав QoS, мы уменьшаем пропускную способность канала по сравнению с тем, что выделяет провайдер, зато можем распределять потоки данных на приоритетные и второстепенные. Сделать это можно двумя способами.

Первый – основательно разобраться в настройках роутера. Несмотря на то, что большинство современных маршрутизаторов имеет встроенную возможность управления потоками интернет-трафика, по умолчанию функция QoS обычно отключена. Установить приоритет трафика для приложений очень просто.

Мы включили QoS в роутере и ограничили скорость работы в файлообменных сетях. Наш киноман продолжает качать, однако, в соответствии с ограничениями приоритета трафика, делает это не так быстро. Результат понятен: остальные пользователи тоже могут пользоваться сетью.


Для всех потоков данных с помощью роутера мы можем установить приоритет по IP-адресу, MAC-адресу, порту и сетевому протоколу.

Для домашнего пользователя этого вполне достаточно. Но перед нами стоит задача посложнее, ведь у нас есть две группы пользователей – гости и служащие отеля. С одной стороны, нам необходимо обеспечить нормальную скорость для гостей, с другой – сделать так, чтобы персонал, активно использующий IP-телефонию, продолжал нормально работать. В подобных случаях лучше воспользоваться специализированным ПО – например, нашим Traffic Inspector. В таком случае будет доступно больше настроек и вам не придётся каждый раз логиниться в админку роутера.


Шейпер Traffic Inspector позволяет не только устанавливать приоритет для приложений, но и управлять скоростью доступа для пользователей, то есть настроить ширину канала.

Кроме индивидуальных настроек для отдельных пользователей, мы можем указать ограничение по скорости работы группы. В этом случае выделенная полоса будет динамически распределяться между активными пользователями, но в рамках их индивидуальных ограничений. В нашем случае мы можем в первой группе (гости) задать низкий приоритет для работы uTorrent, средний – для Skype и высокий – для веб-сёрфинга и другие правила – для второй группы (персонал).

Можно поступить ещё проще: выделить определённую ширину канала для гостей, разделив её поравну между каждым пользователем. Кроме того, для любого правила в шейпере можно назначить расписание, что позволяет динамически изменять настройки в зависимости от времени.

P.S. Если у кого-то есть другие интересные решения описанной проблемы – как говорится, welcome!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Освоение специальности Data Science на Coursera: личный опыт

[unable to retrieve full-text content]




Недавно Владимир Подольский vpodolskiy, аналитик в департаменте по работе с образованием IBS закончил обучение по специализации Data Science  на Coursera. Это набор из 9 курсеровских  курсов от Университета Джонса Хопкинса + дипломная работа, успешное завершение которых дает право на сертификат. Для нашего блога на Хабре он написал подробный пост о своей учебе. Для удобства мы разбили его на 2 части. Добавим, что Владимир  стал еще и редактором проекта по переводу специализации Data Science на русский язык, который весной запустили IBS и ABBYY Language Services.

Часть 1. О специальности Data Science в общих чертах. Курсы: Инструменты анализа данных (программирование на R); Предварительная обработка данных; Документирование процесса обработки данных.

Привет, Хабр!



Не так давно закончился мой 7-месячный марафон по освоению специализации «Наука о данных» (Data Science) на Coursera. Организационные стороны освоения специальности очень точно описаны тут. В своём посте я поделюсь впечатлениями от контента курсов. Надеюсь, после прочтения этой заметки каждый сможет сделать для себя выводы о том, стоит ли тратить время на получение знаний по аналитике данных или нет.

Читать дальше →

Разные типы дисков на одной виртуальной машине: опыт 1cloud

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

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

Зачем это нужно


Разные типы дисков могут требоваться для решения различных задач. Использование дисков различных типов может быть оправданным при создании многоуровневых систем хранения данных — данные, которые часто используются приложениями, можно размещать на более быстрых дисках.

К примеру, если существует сервис, который активно работает с базой данных, то ее имеет смысл перенести на отдельный SSD-диск — это поможет оптимизировать скорость ее работы. При этом, саму операционную систему логично оставить на более медленных SAS-дисках, а для хранения оперативных резервных копий можно подключить к серверу еще более медленный, но дешевый и больший по объёму SATA-диск.

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

Как это работает в 1cloud


Опустим рассказ о процессе закупки и установке дополнительных дисковых полок в наши дисковые массивы, а сразу перейдем к описанию работы новой функции.

Дисковое пространство выдается с массивов NetApp FAS6240 / FAS8040. На уровне vCloud Director организованы отдельные Storage policy под каждый тип. Начиная c vCloud Director версии 5.6 стало доступно (но только через API) изменение storage policy для конкретных дисков.
Для реализации данной функциональности нам пришлось немного пропатчить .net SDK от VMware, поскольку в нем была выявлена ошибка (при добавлении дополнительного диска SDK пытается создать еще одну шину с нулевым идентификатором).
Ниже представлен код, иллюстрирующий корректные настройки для добавления виртуального диска. Также в обязательном порядке нужно реализовать метод для расчета идентификаторов bus и unit (если вам интересна наша реализация метода получения идентификаторов — пишите в комментариях или личным сообщением):

VirtualDisk vDisk = new VirtualDisk(10240, BusType.SCSI, BusSubType.LSI_LOGIC_SAS);
vDisk.GetItemResource().Address = null;
vDisk.GetItemResource().AddressOnParent.Value = unit.ToString(); //unit number, надо вычислять
vDisk.GetItemResource().Parent.Value = bus.ToString(); //это привязка нового диска к существующей шине  


UX для клиентов


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

Аналогичным образом мы стараемся облегчать и процесс заказа услуг — например, покупку нового сервера или создание частной сети можно провести всего в пару кликов. В случае новой функции по заказу дополнительных дисков мы пошли тем же путем — заказать новый накопитель можно со страницы сервера, в закладке «Настройки», подпункт «Диски»:

Планы


Изначально мы планировали добавить возможность дозаказа SSD и SAS дополнительных дисков, но в итоге реализовали и возможность покупки SATA. В наших ближайших планах также реализация облачного хранилища данных, балансировщика нагрузки и возможности заказа физических серверов.

В предыдущих сериях:

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

ОнЛайм за коннект без брака

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

Сети доступа позволяют пользоваться телекоммуникационными услугами миллионам семей, желающим подключить высокоскоростной домашний интернет или выбрать пакет услуг, включающий кабельное телевидение и интернет. За счет чего обеспечивается их надежность, позволяющая нам в любое время пользоваться Интернетом, общаться с друзьями, смотреть ТВ? Что лежит в основе современных телекоммуникационных услуг, благодаря которым абоненты получают доступ в интернет, телевидение, телефонную связь?


В настоящее время «ОнЛайм» предлагает полноценные услуги Triple Play – широкополосный доступ в интернет, телевидение (цифровое и интерактивное) и телефонию. На сети функционируют и другие услуги, например, видеонаблюдение, снятие показаний с домовых счетчиков потребления воды и тепла (для передачи таких данных выделяется отдельная VPN).

Посмотрим, как устроена сеть «ОнЛайм», телеком-провайдера «Ростелеком» в Москве.

Кольцо к кольцу


Магистральная городская сеть построена с использованием кольцевой архитектуры, что позволяет применять механизмы защиты на уровне DWDM (Dense Wavelength-Division Multiplexing — плотное частотное разделение каналов) со временем восстановления менее 50 мс. Кольцевое резервирование сети DWDM позволяет «Онлайму» увеличить надежность, пропускную способность и эффективность её использования, кроме того, это задел на будущее:– основа технической готовности к пропуску растущих объемов трафика.
Она повторяет административное деление Москвы, охватывая все столичные округа.

Каждый магистральный окружной узел включает в себя телевизионную головную станцию и маршрутизатор IP/MPLS. Все 10 окружных узлов связи объединены в физическое кольцо с использованием DWDM, а логически образуют двойную звезду, опирающуюся на два мощных узла ядра сети IP/MPLS. От окружных узлов связи внутри каждого округа расходятся окружные кольца, проходящие через все районы, в свою очередь в каждом из которых располагаются районные узлы связи. Каждое окружное кольцо опирается на два разных окружных магистральных узла. В состав районных узлов входит оборудование агрегирования и районная головная телевизионная станция.  В каждом районе есть также несколько районных колец. Они проходят через весь район. На них опираются так называемые кластеры – небольшие кольца доступа, охватывающие группы из 10 домов. Таким образом, вся эта сетевая структура напоминает ромашку с лепестками в виде окружных, районных колец и колец доступа.

Кольцевая архитектура сети ШПД выбрана не случайно. В сетях кабельного ТВ отказоустойчивость традиционно обеспечивается с помощью кольцевых топологий: телевизионный сигнал в них передается в двух направлениях и контролируется оптическими коммутаторами. Если пропадает сигнал в одном направлении, подхватывается резервное.

В 2007 году совместно с правительством Москвы было принято решение о модернизации телевещания – с крыш домов исчезли антенны, и все телевещание ушло в оптические сети. Сегодня те же сети используются и для передачи данных – широкополосного доступа в интернет. На уровне агрегирования и ядра сети основные протоколы – IP/MPLS, а на уровне сети доступа – протокол Spanning Tree.

В ядре сети ШПД «ОнЛайм» – высокопроизводительное оборудование Alcatel-Lucent  7950 XRS-20 с высокой плотностью портов, те самые два узла, о которых шла речь выше. Именно в эти две точки стекается весь трафик абонентов. Они связаны с «внешним миром» через магистральную сеть «Ростелекома» и основные точки обмена интернет-трафиком: M9, M10, MSK-IX, SPB-IX и др.


Alcatel-Lucent XRS 7950 – мощная платформа для магистральной маршрутизации. Она решает широкий спектр задач, встающих перед операторами. Поддерживая до 160 портов 100GE на одном шасси, маршрутизатор 7950 XRS предоставляет полный спектр возможностей для IP-маршрутизации, MPLS коммутации и создания новых услуг. Система включает 20 слотов с совокупной производительностью каждого 400 Гбит/с и поддерживает интерфейсы 10GE, 40GE, 100GE.


В сети «ОнЛайм» используются стандартные граничные коммутаторы доступа хорошо зарекомендовавших себя вендоров – Edge-CorE, Qtech и др. В масштабах города – это 44 тыс. коммутаторов. Непосредственно к ним «витой парой» подключаются абоненты.

Альтернативные географически разнесенные маршруты помогают достичь высокой отказоустойчивости сети. Единичная авария на уровне округа или района не влияет на её работоспособность. В сети «Ростелекома» все линии закольцованы: порвется в одном месте – сигнал пойдет через кольцо с другой стороны. Лишь при двукратной аварии в кольце часть абонентов на какое-то время могут остаться без связи. Как правило, такие аварии являются маловероятными.

Принцип резервирования заложен и в основу сетей кабельного ТВ. Сейчас в сетях кабельного ТВ «ОнЛайм» 10 окружных и 110 районных станций. На головных станциях для резервирования телесигнала используется эфирная антенна. Центральная головная станция формирует весь телепакет – 50 каналов «обычного» кабельного и более 200 каналов цифрового ТВ. По сети IP/MPLS они передаются на окружную головную станцию, которая по оптической сети раздает их по округу – по каждому району. При выходе окружной стации из строя телесигнал автоматически подхватывается соседней головной стацией и отдается в округ.

Оптические ТВ-коммутаторы на каждой районной телевизионной станции выбирают лучший сигнал из нескольких источников и передают его дальше. Таким образом осуществляется резервирование на каждом участке сети кабельного ТВ. В каждый дом с двух направлений по оптике приходит телесигнал, и двухвходовый оптический приемник отдает сигнал в телевизионную коаксиальную сеть жилого дома. Подробнее о том, как работает интерактивное и цифровое ТВ «ОнЛайм», рассказывается здесь.

Под землёй и в небе


Альтернативные операторы строят в столице свои сети доступа районного уровня с помощью так называемых воздушно-кабельных переходов (ВКП). Эти кабели, протянутые между зданиями, знакомы каждому жителю. Решение не очень эстетичное, зато недорогое — ведь доступ в канализацию обходится недешево. По этой причине канализация используется лишь для обеспечения связности уровня города. В случае сети «ОнЛайм» в ней лежит двойная звезда DWDM (магистральный уровень) и окружные оптические кольца (уровень агрегирования). Возникает закономерный вопрос, насколько надежна вся эта паутина оптических кабелей? На самом деле, обрывы – достаточно редкое явление.

Даже во время ледяного дождя 2010 года ВКП «ОнЛайм» успешно выдержали «испытание на разрыв»: лишь несколько домов на какое-то время остались без связи. Это одна из самых надежных сетей доступа в Москве.

«Тормоза» и сбои в работе


Итак, вся волоконно-оптическая сеть «ОнЛайм», вплоть до домового коммутатора, резервируется. При любой одиночной аварии в кольце связность не теряется. Однако линия от домового коммутатора до абонента не резервируется. Оптоволокно идет только до оборудования компании, установленного в доме. Внутри дома от коммутатора по слаботочному стояку до помещения абонента проложена «витая пара» Категории 5е. Теоретически, это слабое звено. Возможна порча кабеля, обрывы.

Гораздо чаще возникает другая ситуация. Нередко пользователи жалуются на несоответствие реальной скорости передачи данных заявленной провайдером скорости. Между тем в тарифном плане указывается только скоростная характеристика выделенного канала. Не все понимают, что заявленная скорость – это скорость, на которой пользователя «пускают в сеть». То есть скорость внутри сети провайдера. Возможны также незначительные погрешности измерения скорости. К тому же на скорость скачивания влияют самые разные факторы, например, загрузка канала до сервера, загрузка самого сервера, аппаратное и программное обеспечение компьютера. И не факт, что компьютерное оборудование пользователя или его домашняя сеть Wi-Fi такую скорость обеспечивают. Да и не все сайты в интернете достаточно «шустры» для этого. Кстати, после 30 Мбит/с повышение скорости не сделает работу в сети более комфортной – разница будет заметна лишь во времени закачки больших файлов.

Чтобы проводить разъяснительную работу среди пользователей, «ОнЛайм» запустил специальный проект – «Лаборатория скорости». В рамках проекта бригада профессионалов ОнЛайм приезжает домой к абонентам и замеряет скорость интернета. Цель – убедиться, что скорость соответствует заявленной в тарифе.

Перспективы


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

Сеть «Онлайм» все время растет и развивается, сетевое оборудование обновляется продуктами из новых линеек. Специалисты осуществляют постоянный мониторинг загрузки сети – количество подключенных абонентов в разных домах может значительно различаться. Рост абонентской базы (увеличивает нагрузку на сеть доступа. Когда пропускная способность отдельных ее сегментов используется более чем на 80%, пора думать о модернизации – переход на более скоростные интрефейсы 10/100GbE или организации новых каналов передачи данных. Однако архитектура сети ШПД и основные принципы резервирования, обеспечивающие надежность сетевых сервисов, остаются неизменными.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.