Memòria compartida entre processos

Problema:

  • Es requereix afegir una interconnexió amb una aplicació que els seus mètodes públics estan en un format antic, en aquest cas, ActiveX.
  • Aquests controls són exclusivament en 32-bits.
  • La aplicació que els ha de consumir és multi-plataforma i no pot ser condicionada per connectar-se amb aquesta aplicació, el qual és accessòria.

Possibles solucions d’arquitectura:

  • Crear diferents compilacions (no desitjable, complica el codi i obliga a publicar-ne diferents versions)
  • Crear una aplicació en servei del sistema operatiu (malauradament en aquest cas queda descartat ja que el control de ActiveX ens obliga a tenir accés al System.Windows.Forms)
  • Crear una aplicació instanciada des la aplicació principal

Al final he triat la última solució a la qual ens trobem doncs que la intercomunicació entre els processos la podem fer amb diferents mètodes12:

  • Utilitzar un fitxer d’entrada i un altre de sortida entre les aplicacions: potser un mètode eficaç i ràpid, fàcil de transmetre a través de parametrització i lectura de sortida estàndard.
  • Pipes/Canonades(anònims i anomenats)34: ens obligaria a establir un protocol degut a les poques dimensions dels missatges capaços de ser retransmesos per canonades/pipes.
  • Sockets (a través de WCF o Remoting5): complica el codi i ens obliga a establir protocol de funcionament.
  • Per la consola: és el mètode més a pic i pala però el que podem garantir una prova de concepte. El problema és que a major quantitat de dades més lent es torna, mètode lent ja de per si.
  • Memòria compartida67: aquesta es pot fer a través de fitxers mapejats en memòria, el qual també ens facilita la interconnexió i a més a més amb mutex podem establir un nivell de seguretat bastant alta de orde de processament.

La transmissió de dades es pot fer per classes (després fer marshalling) o per serialització (tant JSON com XML). He triat JSON perquè és la que menys ocuparà i en part, perquè si es fan registres d’entrada i sortida ja disposem d’eines per llegir-los i analitzar-los.

Un punt clau són els mutex. Encara que l’ordre de execució dels processos és seqüencial, això ens donarà possibilitat de que evitar que es llegeixin posicions abans de que s’hagin acabat de escriure.

Tant els fitxers com els pipes serà essencial què siguin creats per la aplicació principal: això ens donarà l’avantatge de controlar quan la memòria ha de ser creada i netejada.

La crida a la aplicació que contindrà els accessos a ActiveX es fa mitjançant System.Diagnostics8 aquesta API ens permet a través de StartInfo de aportar paràmetres a la aplicació (propietat arguments), com redirigir el input i output estàndard (propietat StandardOutput.ReadToEnd() per a obtenir el string de sortida).

Codi:

  1. https://stackoverflow.com/a/65731962/2219577 ↩︎
  2. https://stackoverflow.com/a/14140726/2219577 ↩︎
  3. https://learn.microsoft.com/en-us/dotnet/standard/io/pipe-operations ↩︎
  4. https://learn.microsoft.com/en-us/dotnet/api/system.io.pipes.anonymouspipeserverstream ↩︎
  5. https://learn.microsoft.com/en-us/dotnet/api/system.runtime.remoting.channels.ipc.ipcchannel ↩︎
  6. https://learn.microsoft.com/en-us/dotnet/standard/io/memory-mapped-files ↩︎
  7. https://learn.microsoft.com/en-us/dotnet/api/system.io.memorymappedfiles.memorymappedfile.createnew ↩︎
  8. https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process ↩︎

Refresh token in a NSwag C# Client

I use NSwag also to generate the API Clients in VS (using the VS extension REST API Code Generator for VS).

My approach was to override SendAsync using DelegatingHandler. In this handle is possible to catch the response of SendAsync and then manage to refresh the token. I found a kind of implementation in the docs, but in this answer you can find the source of my approach to implement the handle:

I used a global/static variable to store the refresh tokens and other authentication data required.

In my case I had no chance to use dependency injection (DI) as the answer I linked before. Then, before using the service, I create a HttpClient and I assign the CustomDelegatingHandler to this HttpClient.

Primers passos de NET6, C# i Linux

Crides a llibreries

Les crides fins ara es feien des PInvoke a la llibreria de ghostscript de windows. Amb linux no farà falta en si descarregar-afegir la llibreria. També el PInvoke amb linux hi ha unes particularitats 1.

Directoris

Els directoris també canvien. Per a tenir una generació de noms de directoris neta sense directoris a mà, hem de revisar les carpetes especials que fem ús a la API de Path de NET2

Llibreries d’imatges

El fet de no comptar amb System.Drawing dificulta molts dels procesaments de imatge dels que es feia us d’aquesta llibreria de sistema. Cal utilitzar alternatives com Imagesharp o Skiasharp3

Dependències terceres

L’entorn és amb docker/debian per a poder fer el PInvoke a Ghostscript correctament cal instal·lar totes les llibreries següents des el dockerfile:

RUN apt update -y && apt install -y -qq ghostscript && apt install -y -qq libgs9 && apt install -y -qq libgs9-common && apt install -y -qq libgs-dev

  1. https://developers.redhat.com/blog/2016/09/14/pinvoke-in-net-core-rhel ↩︎
  2. https://developers.redhat.com/blog/2018/11/07/dotnet-special-folder-api-linux ↩︎
  3. https://devblogs.microsoft.com/dotnet/net-core-image-processing/ ↩︎

Entity Framework Core i transaccions

Punts claus que cal tenir en compte abans de començar amb las transaccions amb EFCore

Hi ha alguna política activa de reintents (EnableRetryOnFailure):

  • En transaccions estaràs obligat a llançar-les com estratègies d’execució 1
  • Aquestes estratègies estan lligades a la base de dades i al seu proveïdor 2

Tinc MARS (Mutiple Active Results Sets) actius:

  • En transaccions no podràs fer servir punts de retorn (CreateSavePoint, Rollback to SavePoint) 3
  • Per això caldrà reduir els blocs que es fan canvis i evitar les indeterminacions que es poden vaure al fer SaveChanges sobre punts de retorn

  1. https://learn.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency#execution-strategies-and-transactions ↩︎
  2. https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.executionstrategyextensions.execute?view=efcore-7.0#microsoft-entityframeworkcore-executionstrategyextensions-execute(microsoft-entityframeworkcore-storage-iexecutionstrategy-system-action) ↩︎
  3. https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.executionstrategyextensions.execute?view=efcore-7.0#microsoft-entityframeworkcore-executionstrategyextensions-execute(microsoft-entityframeworkcore-storage-iexecutionstrategy-system-action) ↩︎

C# i async

Poques ganes:

bool method(){
   var result = classasync.method().Result;
   return result;
}

Venga va, una oportunitat:

async bool method(){
   var result = await classasync.method();
   return result;
}

Però es que no vull fer-lo asíncron:

bool method(){
   var resultTask = Task.Run(async () => await classasync.method());
   resultTask.Wait();
   return resultTask.Result;
}

Va, donem-li una oportunitat a l’asíncron:

async bool method(){
   var resultTask = Task.Run(async () => await classasync.method());
   resultTask.WaitAsync();
   return resultTask.Result;
}

Ajuda: https://ianvink.wordpress.com/2021/12/12/c-running-an-async-await-task-inside-a-non-async-method-code-block/

Sharepoint i Net 4.5

La API de sharepoint ha canviat i això requereix utilitzar oauth. Podem fer-ho amb el exemple que propociona al tutorial al docs:

https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/using-csom-for-dotnet-standard

  1. Ves al Portal de Azure AD: https://aad.portal.azure.com
  2. Selecciona Azure Active Directory i registres d’aplicacions a l’esquerra
  3. Selecciona New registration
  4. Introdueix un nom per a la aplicació i selecciona Register
  5. Aneu a Permisos de l’API per concedir permisos a la aplicació, seleccioneu “Add a permission”, tria SharePoint, Delegated permissions i selecciona AllSites.Manage
  6. Selecciona Concedir consentiment de l’administrador per consentir els permisos sol·licitats per l’aplicació
  7. Seleccioneu Authentication a l’esquerra
  8. Canvia “Allow public client flows” a Sí
  9. Selecciona Overview i guarda Application ID

Nota: si no seguíssim pas per pas les instruccions de configurar una aplicació a Azure ens podem trobar els següents errors:

A més a més poden sorgir altres errors:

  • El usuari no és vàlid, o no te renovada la contrasenya, o està inactiu: aquestes gestions no seràn possibles ja que no tenim previst fer prompt de la pàgina de AD per fer canvi de contrasenya, etc.

Després cal personalitzar la classe AuthenticationManager que donen al exemple ja que està pensada per a Net Core o superiors, però no NET Framework cal fer un seguit d’adaptacions. Aquí fetes, res del altre món:

Clickonce amb Net6

Funcions de System.Deployment.Application

Aquestes ja no estan disponibles amb NET6 [i als docs se fa referència] però afortunadament es poden simular via lectura de les variables d’entorn [informació en aquesta request a github] , dels fitxers de clickonce i dels directoris propis del desplegament.

Fent servir part del codi d’exemple amb de desplegament de una aplicació NET6 WPF com aquest luncher he fet una llibreria tipificant les constants del desplegament que més fem servir així com simular el update silenciós de clickonce que en realitat no és un altre cosa que instal·lar per enrere i apagar la aplicació un cop està instal·lada la nova versió (el restart de NET Framework ara no funcionarà):

https://github.com/Ruekov/ClickOnceNET6

MSBuild

La preferència de les aplicacions amb NET6 és preferible utilitzar la comanda dotnet msbuild el problema més important en aquest cas és que clickonce es fonamenta bàsicament en NET Framework 3.5 així que haurem d’utilitzar MSBuild.

Com passava ja amb MSBuild i clickonce, els perfils o les ordes de publicació es comporten diferent amb Visual Studio que executades des MSBuild. A més a més, tenim el handicap de que les eines de msbuildtasks de loresoft no acaben de funcionar bé.

Per aquesta raó utilitzant els perfils de publicació (Propierties/PublicationProfiles/ClickOnceProfile.pubxml) amb paràmetres postcompilació afegirem la tasca què:

  • Separi els fitxers de desplegament (setup.exe, xxxxx.application, launcher.exe i carpeta ApplicationFiles)
  • Copi un template del index.html per personalitzar-lo (de nou MSBuild no és capaç de fer-lo posant-hi el WebPageFileName a true)
  • Faci un ZIP amb els continguts del deplegament
	<Target Name="ZipPublishOutput" AfterTargets="ZipDeployment">
		<ItemGroup>
			<LauncherFile Include="$(PublishDir)\Launcher.exe"/>
			<ApplicationFile Include="$(PublishDir)\$(MSBuildProjectName).application"/>
			<SetupFile Include="$(PublishDir)\setup.exe"/>
			<ApplicationFiles Include="$(PublishDir)\Application Files\**\*.*"/>
		</ItemGroup>
		<Copy SourceFiles="$(ProjectDir)\index.html" DestinationFiles="$(PublishDir)\..\..\index.html"/>
		<WriteLinesToFile File="$(PublishDir)\..\..\index.html" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(PublishDir)\..\..\index.html')), ''{AppVersion}'', ''$(AssemblyVersion)''))" Overwrite="true" Encoding="Unicode" />
		<WriteLinesToFile File="$(PublishDir)\..\..\index.html" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(PublishDir)\..\..\index.html')), ''{AppName}'', ''$(MSBuildProjectName)''))" Overwrite="true" Encoding="Unicode" />
		<Copy
			SourceFiles="@(LauncherFile)"
			DestinationFolder="$(PublishDir)\..\..\"
        />
		<Copy
            SourceFiles="@(SetupFile)"
            DestinationFolder="$(PublishDir)\..\..\"
        />
		<Copy
			SourceFiles="@(ApplicationFile)"
			DestinationFolder="$(PublishDir)\..\..\"
        />
		<Copy
            SourceFiles="@(ApplicationFiles)"
            DestinationFolder="$(PublishDir)\..\..\Application Files\%(RecursiveDir)"
        />
		<RemoveDir Directories="$(OutputPath)" />
		<ZipDirectory SourceDirectory="$(PublishDir)\..\..\" DestinationFile="$(PublishDir)\..\..\..\PRO_$(MSBuildProjectName)_$(AssemblyVersion).zip" Overwrite="true" />
	</Target>

Aquest Target haurà de estar especificat al csproj del projecte:

	<Target Name="ZipDeployment">
	</Target>

Al mateix csproj podrem afegir una tasca per mantenir actualitzat als prefils de publicació el número de versió:

<Target Name="updateVersionCLickOnce">
    <PropertyGroup>
        <FilePath>.\Properties\PublishProfiles\ClickOnceProfile.pubxml</FilePathPRO>
    </PropertyGroup>
    <WriteLinesToFile File="$(FilePath)" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(FilePath)')), '&gt;\d+\.\d+\.\d+\.\d+&lt;', ''&gt;$(AssemblyVersion)&lt;''))" Overwrite="true" Encoding="Unicode" /> 
</Target>

Al ser una tasca que hauria de ser executada abans de començar la compilació de la publicació haurem de especificar-la al inici del XML del csproj:

<Project Sdk="Microsoft.NET.Sdk" InitialTargets="updateVersionCLickOnce">

Ara ja podem executar el msbuild generant la sortida correcta i esperada:

msbuild /t:Publish,ZipDeployment /p:DeployOnBuild=true /p:PublishProfile=ClickOnceProfile.xml /restore

Recursos

Entitats amb fitxers: tot junt o per parts?

Hi ha dues propostes per a pujar (actualitzar-les també) unes entitats que porten vinculades a la imatge:

  • [POST] /api/v1/imatge amb un cos html serialitzat tipus { desciption: “”, tags: [“”, “”,””] }
  • [POST] /api/v1/image/{id}/upload amb un multipart/form-www image=@adreçaImatge

Amb:

  • [POST] /api/v1/imatge amb un multipart/form-data description=”” ; tags=[“”,””,””]; image=@adreçaImatge

I aquí és on premia qui ha de fer ús de la API, quina utilització se li dona. El fet de treballar amb entitats sempre és com allò de que per a un martell tot són claus. Hauríem de separar:

  • Amb quina recurrència es pujaran imatges o serà un fet únic?
  • Les aplicacions clients volen crear l’entitat amb la imatge?
  • Les aplicacions clients com processen els multipart/form-data?

De les tres preguntes, les dues primeres són de disseny i la tercera d’arquitectura. Per allò del que els backends poden viure a esquenes de la realitat si ofereixen unes portes definides. Són importants perquè poden encorsetar bastant el processament de les dades (no és el mateix un form què un body serialitzat).

Amb NodeJS tenim multer què a mode intermediari (middleware) ens pot emmagatzemar la imatge en un directori, i posar-nos les dades del form-data al body com a propietats. En el següent exemple es fa servir la segona opció:

routes.js

  app.post("/v1/imatge", imatgeController.uploadImg,  imatgeController.save);

imatgeController.js

const multer = require('multer');

const fs = require('fs');

const imageStorage = multer.diskStorage({
  destination: function (req, file, cb) {

    if (!fs.existsSync('./imageUploads')) {
      fs.mkdirSync('./imageUploads');
    }

    cb(null, './imageUploads');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  }
});

exports.uploadImg = multer({ storage: imageStorage }).single('image');
exports.save = async (req, res) => {

var entity = { description: req.body.description, image: req.file.filename, tags: req.body.tags };
...
}

El fet de que podem sobrecarregar amb diverses funcions les rutes també ens facilitarà altres tipus de processaments (autenticació, per exemple), però això ja és cosa més de NodeJS i ExpressJS.

La solució ens sobrecarrega bastant el què és la entitat: la carrega de imatge per un costat, què pot tenir més lògica (compressió, tractament, desplaçament a un lloc remot) i pot fer més pesat del que és en si el pujar unes dades per a una entitat.

Per a fer un punt comú entre client i servidor, el codi swagger.json descriurà els camps com a formData parant especial atenció en el type:

"/v1/imatges": {
      "post": {
        "tags": [
          "imatges"
        ],
        "summary": "Afegir una imatge",
        "description": "Ruta per afegir una nova imatge",
        "consumes": [
          "image"
        ],
        "parameters": [
          {
            "name": "imatge",
            "in": "formData",
            "description": "afegir nova imatge",
            "required": "true",
            "type": "file"
          },
          {
            "name": "descripcio",
            "in": "formData",
            "description": "descripció de la imatge",
            "required": "true",
            "type": "string"
          }

        ], 
...

Per al client, fem l’exemple de un swagger tipificat i l’utilitzem en un client, en aquest cas, NSwag amb destí C#. El resultat és una funció utilitzable per aquest client:

        /// <summary>Create a banner</summary>
        /// <param name="image">Adding new publication.</param>
        /// <param name="name">Banner name</param>
        /// <param name="isDisabled">Is disabled?</param>
        /// <param name="analyticCode">Analytic Code</param>
        /// <exception cref="SwaggerException">A server side error occurred.</exception>
        public System.Threading.Tasks.Task Banners4Async(FileParameter image, string name, bool isDisabled, string analyticCode)
        {
            return Banners4Async(image, name, isDisabled, analyticCode, System.Threading.CancellationToken.None);
        }

Quina seria la diferència de fer-lo per separat? En un altre post…

Trampes al solitari

Dinahosting per defecte té configurats els servidors per a obligar a rebre peticions des clients web.

Una solució és muntar les capceleres amb aquesta informació:

req.Accept = “text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8″;
req.UserAgent = ” Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0″;

 

Latència de una Azure Function

Revisió gener 2020: Aquesta entrada va ser escrita al 2018 en plena fase de desplegament. Actualment les Azure functions v2 i v3 ofereixen el always on què disminueix dràsticament la latència de les funcions. Les funcions escrites amb Net Framework només poden anar amb V1 que ara mateix està en camí a deprecated.

Des Azure fa un temps els webjobs de les WebApps d’Azure s’estan traslladant a funcions. Això el teu els seus avantatges, com no consumir recursos de les instàncies de WebApp i consumir-ne sota demanda.

Tot és correcte fins que les versions actuals de serveis no ofereixen quelcom bàsic: estar sempre actives. Això és vital qual la funció es dedica a llegir una cua: o pot estar desactiva sempre o pot estar osiosa esperant a respondre. Les Azure Functions al no tenir Always On són el primer cas: desactiva fins que es digui el contrari. Doncs es plantegen dos solucions: (A) fer un pas enrere i tornar a workers o (B) fer un mecanisme de mantenir sempre viu.

Imatge
La latència de les respostes del encadenat de processos és alt degut al temps de warm up de una Azure Function

La solució A implica desplegar una sèrie de llibreries que tenen ús crític de la CPU a una instància WebApp que ja de per si ja té pics de CPU. Descongestionar la instància de aquests pics és una prioritat.

La solució B implica refórmular el codi de lectura de cues per a que quan a la senyal de vida, una senyal que anomenarem keep_alive. Un altre funció Azure inserirà a la cua cada X (definirem el temps com una variable de configuració per a trobar el rendiment òptim) un missatge de keep_alive que els diferents processos encadenats en cues només hauran de retransmetre de la cua d’entrada a la de sortida.

És una forma de mantenir els processos de la cua ociosos, tampoc és una novetat. Són principis que vaig veure a clustering amb heartbeat/pacemaker o més simples, per evitar que el IIS decidís que era bon moment posar-se en repòs.

El problema és què pot saturar les cues (1), pot sobrecarregar els serveis (2) i pot implicar un consum extra (3). Així que caldrà trobar el equilibri entre mantenir una latència optima: una repetició de la senyal keep_alive que no desajusti el sistema i dispari els costos.

Imatge
En vermell: un keep alive cada 10 segons, en blau cada 20 segons, en verd cada 30.

Provem 3 períodes per a l’enviament de keep_alive: cada 30 segons, cada 20 segons i després cada 10. La Azure Function té aproximadament un període de 10 segons per a aixecar-se (warm up a actiu) i de repòs molt menor (al acabar ja passa a repòs).

El fet de mantenir-lo a 30 s’aconsegueix que les peticions intercalades entre les senyals de vida passin de pics de 120 a 60. Segueix sent un temps d’espera elevat per al client, que voldria resoldre les peticions en el menor temps possible.

Reduint el keep_alive a 20 segons reduïm els pics a 30 segons. La meitat de la meitat. I a 10 segons aconseguim que els pics es redueixin a menys de 10 segons.

El curiós de tot això és que són processos que a la versió de desktop mai vam aconseguir que arribessin a aquests números. Les Azure Functions amb una bona conjunció de SQL Server i optimització poden arribar a mostrar molt bons resultats.