Sellest, et WCF proxy saab using-blokiga halvasti läbi..

On saanud harjumuseks IDisposable tüüpe kasutades kohmaka try-finally bloki asemel ilusat using-süntaksit kasutada. Paraku selgus, et Windows Communication Foundation ei ole selle suhtes väga sõbralikult meelestatud juhul kui teenuse tarbimise käigus tekib mõni viga WCF kanaliga. Näiteks:

using (var usingClient = new FtpProxyServiceClient())
{
    usingClient.RecieveFile(target); //this call throws an exception
}

tagastab võrdlemisi kasutu veateate:

System.ServiceModel.CommunicationObjectFaultedException: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.

Server stack trace:
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)

Exception rethrown at [0]:
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ClientBase`1.Close()
   at System.ServiceModel.ClientBase`1.System.IDisposable.Dispose()
   at SomeNameSpace.UIConsole.Program.Test() in W:\SomePath\UIConsole\Program.cs:line 32

Pöörakem tähelepanu, et vea põhjuse või tekkekoha kohta ei ole jälgegi, vaid viga näib tulevat hoopis Dispose() väljakutsumisel. Selgub, et WCF proxy Close() ebaõnnestub kui eelneva vea tõttu on ühendus juba maha võetud. Tulemuseks on UUS viga ja vana unustatakse hoopis.

Lahenduseks on tagasipöördumine try-finally lahenduse poole ja Abort() kasutamine:

var tryClient = new FtpProxyServiceClient();
Boolean succeeded = false;
try
{
    tryClient.RecieveFile(target); //this call throws an exception
    tryClient.Close();
    succeeded = true;
}
finally
{
    if (!succeeded) { tryClient.Abort(); }
}

Ehk vea järel kutsume Close() asemel välja Abort() meetodi. Selle peale saame lõpuks ometi teada ka algse vea põhjuse, asjaosalise stackframe'i jne:

System.ServiceModel.CommunicationException: The maximum message size quota for incoming messages (20) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element. ---> System.ServiceModel.QuotaExceededException: The maximum message size quota for incoming messages (20) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.
   --- End of inner exception stack trace ---

[ - skipped some nonrelevant stacktrace entries - ]

   at SomeNameSpace.Ftp.IFtpProxyService.RecieveFile(Uri targetFile)
   at SomeNameSpace.Ftp.FtpProxyServiceClient.RecieveFile(Uri targetFile) in w:\SomePath\ServiceClients.Ftp\Reference.cs:line 102
   at SomeNameSpace.UIConsole.Program.Test()
                 

Kui jõudlus ei ole probleemiks siis funktsionaalselt enam-vähem samaväärne on õnnestumise jälgimine ära jätta ja Abort() alati välja kutsuda. Viga ta ei viska, aga lisandub väike performance overhead ja see oleks ka lihtsalt vale ;)

Abiks oli Damian McGivern'i postitus, kus muuhulgas vihjatakse, kuidas seda try-finally-succeeded blokki mugavamaks teha saaks.

Huvitav, kas c# 4.0 jaoks on WCF proxy Close() pisut koostööaltimaks tehtud ..

Sellest kuidas Skype kaaperdab HTTPS porti..

Tekkis vajadus vahetada IIS6 all aktiivset Website'i. Selline lihtne tegevus, millest ei oskaks probleemi oodata:

a) site A -> Stop
b) site B -> Start

Site A peatati kenasti, IIS aga keeldub Web Site'i B avamast ja kangekaelselt jutustab:

"This process cannot access the file because it is being used by another process"

Informatiivne on veateates kasutada väljendit "the file", kui ÜHEST konkreetsest failist pole juttugi. Õnneks Eventlog on praktilisem ja source HTTP teatab, et :

Unable to bind to the underlying transport for 0.0.0.0:443. The IP Listen-Only list may contain a reference to an interface which may not exist on this machine.  The data field contains the error number.

Selgus, et probleemiks oli Skype, mis leidis omavoliliselt ja alatult, et 443 kuulub talle. Tegelegu oma asjadega (ja võimaldagu admin-inimestel mugavamalt skype pordid kinni keerata kui vaja).

Pärast Skype ajutist mahatapmist sai IIS (ja mina) oma tööd teha.

Sellest, mida System.Decimal kõhus peidab..

Ilmselt iga asjalikum koodikirjutaja teab, mille poolest erinevad System.Single (või float, kui keegi seda rohkem eelistab) ja System.Decimal andmetüübid. Yada-yada, kahendsüsteem vs kümnendsüsteem. Sellegipoolest jäin mõneks hetkeks mõttesse kui avastasin, et ka ToString() käitub erinevalt:

Console.WriteLine((1.0F).ToString());   // returns '1'
Console.WriteLine((1.000F).ToString()); // returns '1'

Console.WriteLine((1M).ToString());     // returns '1'
Console.WriteLine((1.000M).ToString()); // returns '1,000'
Console.WriteLine(1M == 1.000M);        // returns true

Järeldus tuleviku tarbeks on see, et Decimal korral ei tohi eeldada, et ToString() sama väärtusega sisendi korral alati sama vastuse annaks. Kui formaat on oluline, siis tuleb alati see ka täpsustada ning mitte lootma jääda parameetriteta meetodile.

Console.WriteLine((1.000M).ToString("0"));   
// returns '1'

Loomulikult, kui formaat on märgi täpsusega oluline, siis tuleks ka kultuur täpsustada. Antud juhul ei ole see oluline.

 

Miks Decimal nii käitub ?

Teatavasti on  Decimal 128-bitine struktuur. Sisemiselt:

  • 3 * 32bit "täpsusosa" - low, med and  hi-bits
  • märgibitt
  • scale-väärtusele, mis määrab kui suur osa täpsusosa kümnendkohtadest on murdosa.

Siit ka tuleneb lubatud väärtuste hulk:

{ s * c * 10^(-e)|
     s kuulub hulka {-1,1}, 
     0 <= c <= 2^96 ,
     ja 0 <= e <= 28
}

Muuhulgas nähtub, et seetõttu leiduvad erinevad (s,c,e)-komplektid (ehk decimal sisemised väärtustused), mis omavad muutuja kasutaja poolt vaadatuna sama väärtust (näiteks arvu 1). Eelpool kirjeldatud näide demonstreeris just kahte erinevat sisemist väärtustust.

Illustreerimiseks võib selle näite veel ilmekamalt lahti kirjutada, kasutades konstruktorit, mis võimaldab kõiki eeltoodud komponente ise sisestada:

Decimal d10 = new decimal(10, 0, 0, false, 1);
Console.WriteLine(d10.ToString());   // returns '1,0'
Decimal d1000 = new decimal(1000, 0, 0, false, 3);
Console.WriteLine(d1000.ToString()); // returns '1,000'
Console.WriteLine(d10 == d1000);     //returns True;


Aga mille jaoks on ülejäänud bitid ?

Komponendid e ja märgibitt saavad kasutada 128 - 96 = 32 biti jagu ruumi. Kui märgibitt võtab neist tubli 1, siis miks e lubatud väärtused on piiratud 29 erineva väärtusega, kui kasutada on 2^31 ? Vägisi jääb mulje, et 31 - up(log_2 29) = 26 bitti iga Decimali kohta on kasutu ballast.. Raske uskuda, aga mõistlikku selgitust ei näe.


Aga miks System.Single ToString() ikkagi teisiti käitub ?

Teoreetiliselt on ka nende jaoks võimalik konstrueerida samasugune juhtum (1 * 2^10 vs 2 * 2^9) . Pead või sõrmi panti ei paneks, aga oletan, et levinumate floating-point tüüpide korral lahendatakse "normaliseerimine" riistvaras. Kahendsüsteemis nihutamine on ka oluliselt  odavam ettevõtmine.