Jekyll2022-06-29T21:37:20+00:00https://aensidhe.ru/feed.xmlТестируем raidz2 в боевых условиях2022-06-29T00:00:00+00:002022-06-29T00:00:00+00:00https://aensidhe.ru/blog/raidz2<p>Есть у меня дома небольшой сервачок с хранилищем под всякие фотки и прочую относительно полезную информацию.
Ну, чтобы там всякие санкции и прочее не мешали жить. Построен на основе Ubuntu + zfs-on-linux. Выбрал в своё время
я конфигурацию raidz2 - это когда массив может потерять 2 диска и продолжить работу. Всего у меня шесть дисков.</p>
<p>Параллельно я использую сервачок для всяких работ вида “взять HDD из старого ноута и перенести всё на SSD”. Ну и вот
в марте я это делал первый раз, ну и лень было искать кабели, выдернул два из дисков рейда, всё сделал и забыл.
В следующий раз мне это потребовалось сделать на днях и я осознал ситуацию-то: сервак 3 месяца работает в минимально
допустимой конфигурации, но работает.</p>
<p>Подключил я обратно два диска, массив за 15 минут перестроился обратно и всё заработало ещё лучше. Так и живём.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruоно таки работает!Мы ищем таланты!2022-06-15T00:00:00+00:002022-06-15T00:00:00+00:00https://aensidhe.ru/blog/%D0%B2%D0%B0%D0%BA%D0%B0%D0%BD%D1%81%D0%B8%D1%8F-%D0%9F%D0%9C%D0%B0<p>Мы в ОЗОНе продолжаем искать новые таланты, несмотря ни на что, и конкретно ко мне в отдел есть <a href="https://job.ozon.ru/vacancy/?specialization=ERP%20и%20учетные%20системы">куча вакансий программистов и тестировщиков</a>. По ссылке не всё моё, но многое. Но порекламировать я хотел не эту вакансию, а вакансию руководителя проектов. В течение недели она появится и на сайте. Работать придётся непосредственно со мной. Можно удалённо, но тогда надо быть онлайн с 10 до 19 +/-. Ниже будет формальное описание вакансии. К сожалению, по формальным политикам, публиковать вилку не могу - приходите ко мне в личку или в почту, обсудим всё.</p>
<p>Следует понимать, что из требуемых навыков я буду хотеть всё или почти всё, из желаемых - как повезёт.</p>
<h2 id="вакансия">Вакансия</h2>
<h3 id="кто-мы">Кто мы?</h3>
<p>Мы - отдел разработки Метазон 2. Занимаемся учётом всего, что происходит с продавцами в ОЗОНе.</p>
<h3 id="что-надо-делать">Что надо делать?</h3>
<p>Команда растёт и бизнес-заказчиков становится всё больше. Если раньше был один технический комитет, то сейчас их уже три и, возможно, будет четыре. Каждый технический комитет взвешивает заявки от бизнес-заказчиков и выбирает те, которые имеют наивысший приоритет. В связи с этим есть рутина, которую надо вести. Из этих заявок формируются бэклоги нескольких команд, необходимо следить за состоянием бэклогов - всё должно быть в нужном порядке и оценено.</p>
<p>Заниматься оценкой задач, распределением их внутри команды, церемониями и ритуалами внутри команд - не надо, они это смогут сделать сами.</p>
<p>Помимо этого, требуется предоставлять сервис одного окна для бизнес-заказчиков:</p>
<ul>
<li>в каком состоянии мой проект?</li>
<li>что мне надо сделать, чтобы мой проект был сделан в такие-то сроки?</li>
</ul>
<p>Надо уметь говорить бизнес-заказчикам “нет” и требовать с них необходимые для работы данные. Резюмируем:</p>
<h3 id="обязанности">Обязанности</h3>
<ul>
<li>Предкомитетная подготовка заказчиков</li>
<li>Ведение файла технического комитета</li>
<li>Ведение статусов проектов</li>
<li>Ведение диаграмм Ганта</li>
<li>Общение с заказчиком</li>
<li>Контроль наличия оценок и сроков на задачах как на наших командах, так и у смежников</li>
</ul>
<h3 id="требуемые-навыки">Требуемые навыки</h3>
<ul>
<li>Опыт работы ПМ от 1 года</li>
<li>Хорошие коммуникативные навыки</li>
<li>Умение работать с диаграммами Ганта</li>
<li>Умение работать с Jira, Confluence</li>
<li>Стрессоустойчивость</li>
<li>Аккуратность</li>
<li>Технический бэкграунд: понимание цикла разработки ПО</li>
</ul>
<h3 id="желаемые-навыки">Желаемые навыки</h3>
<ul>
<li>Structure plugin for JIRA</li>
<li>Понимание как работает Git (в общих чертах)</li>
<li>Понимание микросервисной архитектуры</li>
<li>Понимание финансового и/или бухгалтерского учёта будет преимуществом</li>
</ul>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruА конкретно - руководителя проектовWhat’s in your EDC?2021-04-21T00:00:00+00:002021-04-21T00:00:00+00:00https://aensidhe.ru/blog/check-yourself<p>Disclaimer: I live in nine-floor house, adapt to your case.</p>
<p>Have you tried this exercise: fire evacuation from the building? You should pay specific attention to items that you grabbed doing it first time and time you spent on it. It took me around five minutes (forgot to do exact timing) to evacute last weekend and some items that I’ve grabbed should be left behind and there’re items that I’ve missed. Before you ask: I’m fine, we’re ok. So, having said that, there’s list of what you should have, from my point of view:</p>
<ul>
<li>flashlight (you will need your battery in phone) - check and replace batteries on regular basis</li>
<li>id - mine was in another jacket, not in a folder with a documents</li>
<li>any papers that are important for you (real estate, car, etc) - everything should be in one place known to you</li>
<li>cash</li>
<li>necessary equipment - I’ve grabbed laptop</li>
<li>proper clothing, if you have time (let’s say, fire is several floors above you)</li>
<li>alert neighbours, help each other to evacuate eldes, children, people’s with disabilities</li>
</ul>
<p>Do not forget to cut off natural gas, if you have it. Don’t forget to cut off power.</p>
<p>Take your time and learn how to do proper fire evacuation in your area: when to leave building, when to barricade. Use of elevators is usually prohibited. Take this (no elevators) into account, consider your kids, cats, dogs, etc and edit list of belongings you plan to take with you. I also thought all my life that I would never need it.</p>
<p>Be prepared, that during extinguishing a fire your apartment will be flooded. Fire was on 6th floor, apartments on 5th and 4th floors were totalled. 3rd floor was damaged. 2nd floor is ok. Power damaged all the way through 1st floor.</p>
<p>Contact your fire department and learn, don’t use my advise blindly and stay safe.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruor what to grab when building is on fireВремя собирать камни и время разбрасывать камни2021-01-27T00:00:00+00:002021-01-27T00:00:00+00:00https://aensidhe.ru/blog/moving-on<p>Хотел написать этот пост в годовщину, 25го, но таки был занят. Почти ровно шесть лет назад я сошёл с поезда в Минске, чтобы 26го выйти на работу в проект eVote. Проект привёл к куче новых знакомств и новых друзей. А 12го января 2021го года я отдал ключи от квартиры хозяйке и теперь в Минске я буду разве что туристом или в командировку. Надеюсь, регулярно :)</p>
<p>Помимо этого, я наконец получил все доки и отстрелял своё ружьишко. В мишень №6 на 25 метров попадаю средненько, но терпимо. Для улучшения результат надо хотя бы не забывать надевать линзы или очки. Первый сейф купил я лет десять назад, или того более, а потом всё как заверте… и, в общем, вот только сейчас руки дошли. В целом, доволен.</p>
<p>Вот как-то так прошёл мой 2020й, строго говоря, жаловаться не на что. Все, слава богу, живы, здоровы, чего и вам желаю. Ну и чтоб работа была, хорошая и интересная.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruили итоги 2020How to ruin technical community?2020-08-09T00:00:00+00:002020-08-09T00:00:00+00:00https://aensidhe.ru/blog/how-to-ruin-technical-community<p>Since yesteday (this translation is being posted one day later after original russian post) I’m no longer a leader of dotnetby community. All powers where given back to <a href="https://www.facebook.com/Ivan.Antsipau">Ivan Antsipau</a>, who, being in US, knows better how to run community in Belarus. I always held a position, shared by all good technical communities in the world: we’re outside of politics. All such discussions should be held in backstage channels, outside of main means of communication. This is important because members of community could have different views not only on who should we vote for, but also on what economical formation we should have. And I stated my position <a href="https://t.me/dotnetby/20583">here, russian</a>. <a href="/images/blog/2020/08/aensidhe.png">Screenshot</a>.</p>
<p>Ivan, when he stepped down as a leader of community, didn’t step down as an admin of group in facebook and chat in telegram. Ivan, as a truly democratic person, didn’t ask anyone and started political agitation specifically for one candidate. And he asked to go vote for his sake. <a href="https://t.me/dotnetby/20775">Link, russian</a>. <a href="/images/blog/2020/08/ivan.png">Screenshot</a>. I couldn’t ban him because of how Telegram works (Ivan became an admin before me), hence I can’t enforce my promise that chat will be free from politics. Therefore I decided that I could not stand being on same board with other managers of this community. Our partners at Eventspace.BY were informed immediately. If you have any questions about dotnetby user group - ask Ivan directly.</p>
<p>Other admins of the chat urge anybody who’s not <code class="highlighter-rouge">true belarussian</code> (meaning you don’t vote for candidate selected by them and Ivan) to leave group as well. <a href="https://t.me/dotnetby/20780">Link, russian</a>. <a href="/images/blog/2020/08/artem.png">Скриншот</a>. Dear Arciom, if you wasn’t aware, out community before your message was an international one. If you want your community to be national and strictly belarusian one, you can continue to do what you do.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruStart political agitation there1-pixel-wealth2020-05-02T00:00:00+00:002020-05-02T00:00:00+00:00https://aensidhe.ru/blog/1-pixel-wealth<p>TL;DR: <a href="/1-pixel-wealth/">Богатство в масштабе</a></p>
<p>Вчера, 1 мая, был день солидарности трудящихся. С чем вас, коллеги я и поздравляю. Я провёл этот день с семьёй и в трудах над переводом одного интересного проекта, который нашёл <a href="https://www.facebook.com/andrii.kozak">aaa13</a>: <a href="https://mkorostoff.github.io/1-pixel-wealth/">Wealth shown to scale</a>. Проект показывает разницу между доходами капиталистов и пролетариев достаточно наглядно. Некоторые вещи были неочевидны в том числе и мне.</p>
<p>Перевод будет жить <a href="/1-pixel-wealth/">отдельной страничкой</a> на моём сайте. Рекомендую к прочтению и показу молодому поколению. Огромное спасибо <a href="https://github.com/AnastasiaPetrovna">AnastasiaPetrovna</a> за редактуру, семьей и UR-сообществу за общую помощь.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruМасштабы неравенства на планетеSick kid, broken promises and bad design decisions.2020-04-09T00:00:00+00:002020-04-09T00:00:00+00:00https://aensidhe.ru/blog/sick-kid-broken-promises-and-bad-design<p>So, I’m on sick leave caring for my daughter. But she’s sleeping, so I have some time to play with my very small service: one query to mysql database, format results to CSV, that’s it. And it works on my machine. But it fails in cloud, we’re getting 500 on endpoint with CSV and, most interesting, we don’t have logs at all.</p>
<p>That service is a test bed to some middlewares and formatters that I’m going to promote for use across the company. So, now I’m on a journey, on an adventure: how to find out what’s going on in Fargate container, without any logs and abilities to attach my remote debugger. To make things more interesting, I built that application as self-contained, ready-to-run .net core 3.1 application.</p>
<p>Fortunately, problem was easy to replicate in local docker container. No logs, 500, same as in cloud. We’re using rider to develop .net applications. Unfortunately, Rider (at the moment of writing) can’t debug ready-to-run applications. It can debug apps in docker, though. Ok, let’s fallback to VS2019: fortunately it can debug ready-to-run application. One of devs from Jetbrains team said that they will try to ship debug of ready-to-run applications in release after 2020.1. But no promises were made and that’s understandable. Still, we’re waiting for this feature.</p>
<p>VS2019 has an ability to attach debugger to a process inside of container. Probably, even remote ones, I’ve never tested it yet. I’m using <code class="highlighter-rouge">FROM mcr.microsoft.com/dotnet/core/runtime-deps:3.1.3-buster-slim</code> as base image. It doesn’t have curl or wget in there, so you need to install one of those by yourself to make that remote debugging work. And yes, VS2019 can debug ready-to-run applications. And we’re getting this exception in VS2019:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">MySql.Data.MySqlClient.MySqlException (0x80004005): SSL Authentication Error
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
---> Interop+Crypto+OpenSslCryptographicException: error:1425F102:SSL routines:ssl_choose_client_version:unsupported protocol
--- End of inner exception stack trace ---
at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, Byte[] recvBuf, Int32 recvOffset, Int32 recvCount, Byte[]& sendBuf, Int32& sendCount)
at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteContext& context, ArraySegment`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions)
--- End of inner exception stack trace ---
at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at MySqlConnector.Core.ServerSession.InitSslAsync(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, SslProtocols sslProtocols, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1270
at MySqlConnector.Core.ServerSession.InitSslAsync(ProtocolCapabilities serverCapabilities, ConnectionSettings cs, SslProtocols sslProtocols, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1297
at MySqlConnector.Core.ServerSession.ConnectAsync(ConnectionSettings cs, Int32 startTickCount, ILoadBalancer loadBalancer, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 401
at MySqlConnector.Core.ConnectionPool.GetSessionAsync(MySqlConnection connection, Int32 startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ConnectionPool.cs:line 112
at MySqlConnector.Core.ConnectionPool.GetSessionAsync(MySqlConnection connection, Int32 startTickCount, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\Core\ConnectionPool.cs:line 141
at MySql.Data.MySqlClient.MySqlConnection.CreateSessionAsync(ConnectionPool pool, Int32 startTickCount, Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\MySql.Data.MySqlClient\MySqlConnection.cs:line 645
at MySql.Data.MySqlClient.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:\projects\mysqlconnector\src\MySqlConnector\MySql.Data.MySqlClient\MySqlConnection.cs:line 312
at Dapper.SqlMapper.QueryAsync[T](IDbConnection cnn, Type effectiveType, CommandDefinition command) in C:\projects\dapper\Dapper\SqlMapper.Async.cs:line 419
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 53
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 58
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 58
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)</code></pre></figure>
<p>So, problem, obviously lies somewhere in connection to mysql. But why doesn’t exception appear in logs? Why? This is actually important, because we could miss logs from production and I hate that. At this time I’ve already spent 4 hours dissecting my 200 lines service, trying to understand - who mutes my exception, who deserves most painful execution in history of mankind?</p>
<p>And finally, I have removed JsonFormatter (we have custom json formatter for Serilog, tailored to our company needs) from logger configuration and, voila, we see that beatiful exception as above. “Hmmmm”, I thought, - “What happened? JsonFormatter is dumb and simple”. So I injected the test code into controller and I’ve got another exception:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">System.NullReferenceException: Object reference not set to an instance of an object.
at Serilog.Formatting.Json.JsonValueFormatter.WriteQuotedJsonString(String str, TextWriter output)
at JsonFormatter.LogException(Exception exception, TextWriter output) in /app/src/Logger/JsonFormatter.cs:line 56
at JsonFormatter.LogException(Exception exception, TextWriter output) in /app/src/Logger/JsonFormatter.cs:line 74
at JsonFormatter.LogException(Exception exception, TextWriter output) in /app/src/Logger/JsonFormatter.cs:line 74
at JsonFormatter.LogException(Exception exception, TextWriter output) in /app/src/Logger/JsonFormatter.cs:line 74
at JsonFormatter.Format(LogEvent logEvent, TextWriter output) in /app/src/Logger/JsonFormatter.cs:line 29
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 61
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 68
at HistoryController.Index(Int32 accountId) in /app/src/history-service/HistoryController.cs:line 68
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.Invoke(HttpContext context)
at App.Metrics.AspNetCore.Tracking.Middleware.ApdexMiddleware.Invoke(HttpContext context)
at App.Metrics.AspNetCore.Tracking.Middleware.PerRequestTimerMiddleware.Invoke(HttpContext context)
at App.Metrics.AspNetCore.Tracking.Middleware.RequestTimerMiddleware.Invoke(HttpContext context)
at App.Metrics.AspNetCore.Tracking.Middleware.ErrorRequestMeterMiddleware.Invoke(HttpContext context)
at App.Metrics.AspNetCore.Tracking.Middleware.ActiveRequestCounterEndpointMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)</code></pre></figure>
<p>Gotcha! Line 56 is where we’re passing stacktrace to serilog method that should format a string. That <a href="https://github.com/serilog/serilog/blob/8ae332d983b31044f4da0fa34e3b9cb85ba68bc9/src/Serilog/Formatting/Json/JsonValueFormatter.cs#L298">method</a> assumes that passed string isn’t null. And this is public API. So, let’s talk about broken promises. We’re getting to broken promises part of the
story.</p>
<p>In my project file I have this code:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="nt"><PropertyGroup></span>
<span class="nt"><OutputType></span>Exe<span class="nt"></OutputType></span>
<span class="nt"><TargetFramework></span>netcoreapp3.1<span class="nt"></TargetFramework></span>
<span class="nt"><LangVersion></span>latest<span class="nt"></LangVersion></span>
<span class="nt"><Nullable></span>enable<span class="nt"></Nullable></span>
<span class="nt"><WarningsAsErrors></span>true<span class="nt"></WarningsAsErrors></span>
<span class="nt"><TreatWarningsAsErrors></span>true<span class="nt"></TreatWarningsAsErrors></span>
<span class="nt"></PropertyGroup></span></code></pre></figure>
<p>As you can see, nullable feature of C#8 is enabled. All warnings are propagated to errors. Nuget package with logger was published, that means that compilation was successful, that means that we do not pass anywhere something that is nullable to non-nullable APIs, right? Let’s look to signature of the method: <code class="highlighter-rouge">public static void WriteQuotedJsonString(string str, TextWriter output)</code></p>
<p><code class="highlighter-rouge">str</code> isn’t nullable! There is no question mark there! So, if <a href="https://docs.microsoft.com/en-us/dotnet/api/system.exception.stacktrace?view=netcore-3.1">stacktrace</a> property is nullable, compiler should emit error in our case. But it doesn’t. Promise about “null safety” is broken. But why? Answer to that is third part of my story. It’s about design decisions. You see, C# is a language, one of many in .net world. And feature of nullable reference types was introduced on language level, not on runtime level. That means that nullable string and non-nullable string to runtime are the same types. There is no real difference. And compiler emits some attributes on types to mark them either nullable or not. So, Serilog package was compiled either before that feature was introduced to C#, or with disabled feature, so no attributes on types.</p>
<p>And that was a story about bad design decisions. From my point of view, being able to check if type is nullable or not on runtime level is very important and worth of breaking backward compatibility. Current implementation, where <code class="highlighter-rouge">string</code> and <code class="highlighter-rouge">string?</code> are essentially same types, with no real ability of differintiate between them - is just bad decision, which led me, and will lead
other people, into false sense of safety: “see, zero errors with nullable enabled! We can’t get NRE!” “Yes, sweet summer child, you can”.</p>
<p>End of story.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruHow nullable feature of C#8 led me into false safety.Волшебное яйцо2020-02-10T00:00:00+00:002020-02-10T00:00:00+00:00https://aensidhe.ru/blog/twister-egg<p>Несколько месяцев назад в <a href="https://www.facebook.com/aensidhe/posts/2693547320656939">фейсбуке</a> я обещал рассказать о полезных девайсах для родителей. Вот, выполняю обещанное и даже не через три года!</p>
<p>Дочь у меня на искусственном вскармливании с первого дня, поэтому надо регулярно (до шести раз в день) готовить смесь. Смесь требует температуры воды 40 градусов, но воду сначала надо вскипятить. Способов доступно не очень много: охлаждать под струёй холодной воды, ждать пока остынет, купить спец.девайс для приготовления смеси.</p>
<p>Ждать - это долго, час-полтора и непредсказуемо. Поэтому данный способ был отметён сразу. Спецдевайс для приготовления смеси под ключ стоит как крыло от самолёта - задушила жаба. Поэтому поначалу занимались охлаждением под струёй холодной воды. У этого способа тоже есть минусы: это достаточно долго (минут 10), не очень предсказуемо, ну и воды выливается дофига. Это стоит денег и не очень экологично. Поэтому у меня родилась идея о том, чтобы построить что-то типа системы водяного охлаждения и использовать её для достижения цели.</p>
<p>Но мы решили сначала проверить не придумал ли кто-то это до нас и, оказывается, придумал. Девайс называется <a href="https://www.nip.family/de/produkte/zubehoer/cool-twister">Cool Twister</a> и позволяет остудить 210 мл воды до 40, 50, 60 или 70 градусов. Делает он это достаточно за полторы минуты и экономит время, воду и нервы. Одна из лучших покупок в 2019м году, всем рекомендую.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruИли как охлаждать кипяток до 40 градусов быстроWho’s there?2019-06-06T00:00:00+00:002019-06-06T00:00:00+00:00https://aensidhe.ru/blog/back-to-blogging-2<p>Almost half-year no one wrote here. Let’s fix that!</p>
<p>There were a lot going one in my life last year. I became director of engineering. Completely another world, if you compare that to world of developer. Very interesting. I’m still fascinated by all that stuff.</p>
<p>Still, that wasn’t main reason to cease almost all OSS activity and blogging. My daughter was born this march. That brings in more things to do, more responsibility. All that means that it is unknown when I can start to write OSS again. Hopefully, I find some time this summer.</p>
<p>Life is hard. Nevertheless, it’s beautiful. Hope you all will have nice summer.</p>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruLet's try to blog again.ReadOnlySpan, ReadOnlySequence, C++2019-01-12T00:00:00+00:002019-01-12T00:00:00+00:00https://aensidhe.ru/blog/readonlyspan-readonlysequence-c++<p>There are two similar but different types in .net: <a href="https://docs.microsoft.com/en-us/dotnet/api/system.readonlyspan-1">ReadOnlySpan<T></a> и <a href="https://docs.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequence-1">ReadOnlySequence<T></a>. Former is an abstraction over continous array of elements of T that you can’t change. Latter is chain from those arrays. Former is useful when you’re writing code that accepts arrays: usual arrays, stack-allocated arrays (hello, stackalloc), arrays in unmanaged memory and so on. It has some overhead, which was discussed <a href="/blog/know-what-are-you-going-to-benchmark/">earlier</a>.</p>
<p><code class="highlighter-rouge">ReadOnlySequence<T></code>, on my opinion, is most useful when you have some network IO, because when you expect million (or even thousand) of 64bit integers from network, you can’t assume that you’ll get that numbers in one batch. I mean, you can assume, but it won’t be that way everytime. In most cases, you’ll get chain of buffers.</p>
<p>I try to write library which will be useful in both cases. And we stumble upon particular weakness of C#: we need to write same method twice. Look to excerpt, full code is <a href="https://gist.github.com/aensidhe/439d801227a6b25bad062493da97901b">here</a>.</p>
<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">private</span> <span class="k">readonly</span> <span class="n">IMsgPackSequenceParser</span><span class="p"><</span><span class="n">TElement</span><span class="p">></span> <span class="n">_elementSequenceParser</span><span class="p">;</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">Read</span><span class="p">(</span><span class="n">ReadOnlySequence</span><span class="p"><</span><span class="kt">byte</span><span class="p">></span> <span class="n">source</span><span class="p">,</span> <span class="n">Span</span><span class="p"><</span><span class="n">TElement</span><span class="p">></span> <span class="n">array</span><span class="p">,</span> <span class="k">ref</span> <span class="kt">int</span> <span class="n">readSize</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">array</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="n">array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">_elementSequenceParser</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="nf">Slice</span><span class="p">(</span><span class="n">readSize</span><span class="p">),</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">temp</span><span class="p">);</span>
<span class="n">readSize</span> <span class="p">+=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">readonly</span> <span class="n">IMsgPackParser</span><span class="p"><</span><span class="n">TElement</span><span class="p">></span> <span class="n">_elementParser</span><span class="p">;</span>
<span class="k">private</span> <span class="k">void</span> <span class="nf">Read</span><span class="p">(</span><span class="n">ReadOnlySpan</span><span class="p"><</span><span class="kt">byte</span><span class="p">></span> <span class="n">source</span><span class="p">,</span> <span class="n">Span</span><span class="p"><</span><span class="n">TElement</span><span class="p">></span> <span class="n">array</span><span class="p">,</span> <span class="k">ref</span> <span class="kt">int</span> <span class="n">readSize</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">array</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="n">array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">=</span> <span class="n">_elementParser</span><span class="p">.</span><span class="nf">Parse</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="nf">Slice</span><span class="p">(</span><span class="n">readSize</span><span class="p">),</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">temp</span><span class="p">);</span>
<span class="n">readSize</span> <span class="p">+=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We have problems:</p>
<ul>
<li>Both types are structs. They don’t implement any interfaces, so, we can’t write any common code for some <code class="highlighter-rouge">IReadOnlyCollection<T></code>.</li>
<li>We can’t make this code generic, because <code class="highlighter-rouge">ReadOnlySpan<T></code> is special stack-only struct, which can’t be type parameter of generic method.</li>
<li>We can’t create <code class="highlighter-rouge">ReadOnlySequence<T></code> from <code class="highlighter-rouge">ReadOnlySpan<T></code> without copy, because <code class="highlighter-rouge">ReadOnlySequence<T></code> is not made from spans, it is made from <code class="highlighter-rouge">ReadOnlyMemory<T></code> which is similar to Span, but not stack-only, and creating memory from span involves copying.</li>
<li>We can’t change signature <code class="highlighter-rouge">Read</code> by replacing <code class="highlighter-rouge">ReadOnlySpan<T></code> by <code class="highlighter-rouge">ReadOnlyMemory<T></code>, because they’re different. <code class="highlighter-rouge">ReadOnlyMemory<T></code> can’t be used to work with unmanaged memory or stack-allocated array. So, <code class="highlighter-rouge">ReadOnlySpan<T></code> can represent a lot more arrays.</li>
<li>You can’t represent sequence by span for obvious reasons.</li>
</ul>
<p>I don’t see any solution to that problem, except duplicating code. I remember when we have good old C++, when you can do this (thanks, <a href="https://eao197.blogspot.com/">Yauheni Akhotnikau</a> for corrected code):</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="k">template</span><span class="o"><</span><span class="k">class</span> <span class="nc">TElement</span><span class="p">></span>
<span class="k">class</span> <span class="nc">Parser</span> <span class="p">{</span>
<span class="nl">private:</span>
<span class="k">const</span> <span class="n">IMsgPackParser</span><span class="o"><</span><span class="n">TElement</span><span class="o">></span> <span class="n">elementParser_</span><span class="p">;</span>
<span class="k">template</span><span class="o"><</span><span class="k">template</span><span class="o"><</span><span class="k">class</span><span class="p">></span> <span class="n">Container</span><span class="p">></span>
<span class="kt">void</span> <span class="n">Read</span><span class="p">(</span><span class="k">const</span> <span class="n">Container</span><span class="o"><</span><span class="n">byte</span><span class="o">></span> <span class="o">&</span> <span class="n">source</span><span class="p">,</span> <span class="n">Span</span><span class="o"><</span><span class="n">TElement</span><span class="o">></span> <span class="o">&</span> <span class="n">array</span><span class="p">,</span> <span class="kt">size_t</span> <span class="o">&</span> <span class="n">readSize</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0u</span><span class="p">;</span> <span class="n">i</span> <span class="o">!=</span> <span class="n">array</span><span class="p">.</span><span class="n">Length</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">size_t</span> <span class="n">temp</span><span class="p">;</span>
<span class="n">array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">elementParser_</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="n">Slice</span><span class="p">(</span><span class="n">readSize</span><span class="p">),</span> <span class="n">temp</span><span class="p">);</span>
<span class="n">readSize</span> <span class="o">+=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span></code></pre></figure>Anatoly 'Aen Sidhe' Popovme@aensidhe.ruWhen you long for С++ templates