Expliciete casting van double naar int met verschillende resultaten

We gebruiken een API die dit in feite doet

var t = TimeSpan.MaxValue;
int x = (int)t.TotalMilliseconds;

waarbij x uiteindelijk wordt doorgegeven aan System.Threading.WaitHandle.WaitOne (int).

Het probleem is wanneer deze code in onze dev- en staging-omgevingen wordt uitgevoerd, maar er worden geen fouten gegenereerd, maar als deze in de productie wordt uitgevoerd, wordt deze wel gegooid:

Exception: System.ArgumentOutOfRangeException
Message: Number must be either non-negative and less than or equal to Int32.MaxValue or -1.
Parameter name: millisecondsTimeout

Wanneer ik dit test met een eenvoudige consoleapp (zowel x86 als x64) het resultaat van x = -2147483648 (int.MinValue) terwijl wanneer ik de code in het directe venster voer krijg ik x = 1566804069.

Wat is er aan de hand?

Opmerking: staging en productie worden allemaal gekloond vanuit één VM, dus er zijn geen verschillen tussen beide

THIS IS CODE THAT WE CANNOT CHANGE! Otherwise I wouldn't be asking this question.

3
@Tony Ik heb niet genoeg info, daarom heb ik gepost. Dat is heel raar. Ik heb een foutenrapport ingediend. We zijn niet de enige mensen die deze API gebruiken, dus er is iets vreemds als niemand het heeft gemeld, maar het is duidelijk een verkeerde code.
toegevoegd de auteur Dustin Davis, de bron
@Ramhound, de vraag zegt, vetgedrukt: "DIT IS CODE DIE WE NIET KUNNEN VERANDEREN! Anders zou ik deze vraag niet stellen."
toegevoegd de auteur phoog, de bron
@Ramhound het was er toen ik deze pagina voor het eerst opende ten minste 15 minuten geleden.
toegevoegd de auteur phoog, de bron
@Ramhound OK, maar toen ik je reactie las, bleek dat je reactie een paar minuten eerder was toegevoegd. Schrap je je opmerkingen en plaats je ze opnieuw? Het is erg verwarrend om dat te doen als we het hebben over dingen die afhankelijk zijn van de tijd. De opmerking die ik 6 minuten geleden heb beantwoord, lijkt nu bijvoorbeeld 2 minuten geleden te zijn geplaatst.
toegevoegd de auteur phoog, de bron
@Tony nee, als het was aangevinkt zou het een overflow-uitzondering zijn; de geciteerde uitzondering suggereert ofwel dat de duur inpakken is omdat niet aangevinkt , of dat het interval om te beginnen negatief was.
toegevoegd de auteur Marc Gravell, de bron
@Tony als aangevinkt versus niet aangevinkt het probleem was, zou het inderdaad een fout zijn, maar het zou een andere fout zijn ( OverflowException )
toegevoegd de auteur Marc Gravell, de bron
@Dustin dat niet gaat werken; de maximale tijdspanne is 10.675.199 dagen; int.MaxValue in milliseconden is 25 dagen. Dat zal niet passen! -
toegevoegd de auteur Marc Gravell, de bron
Hoewel de VM voor beide systemen hetzelfde is, hoe zit het dan met de onderliggende hardware? Gebruiken ze verschillende CPU's?
toegevoegd de auteur Gabe, de bron
Uw conversie loopt over, dat is waarom u twee verschillende resultaten krijgt, type cast to long of Int64
toegevoegd de auteur Emmanuel N, de bron
Wacht eens even, toen je de console-app maakte die je zei dat het werkt? Maar je zei ook dat het een negatieve waarde doorgeeft. Bent u van mening dat uw testconsole-app WaitOne() een negatieve waarde heeft gegeven en geen uitzondering heeft gegenereerd?
toegevoegd de auteur Dylan Smith, de bron
Ik heb mijn opmerkingen verwijderd.
toegevoegd de auteur Security Hound, de bron
@phoog - Hij heeft het pas recent toegevoegd.
toegevoegd de auteur Security Hound, de bron
Mijn opmerkingen verwijderd en een antwoord met een mogelijke oplossing toegevoegd.
toegevoegd de auteur Tony Lee, de bron
@DustinDavis - Ik denk dat Gabe de juiste vraag stelde - ondersteunt het productiesysteem Intel SSE2. Op mijn computer wordt cvttsd2si (vereist SSE2) gebruikt om te converteren, waardoor de Int.MinValue wordt geproduceerd.
toegevoegd de auteur Tony Lee, de bron
@Marc - Ik ben traag. Ik denk dat dat betekent dat deze vraag niet genoeg informatie heeft.
toegevoegd de auteur Tony Lee, de bron

4 antwoord

TimeSpan.MaxValue.TotalMilliseconds is een dubbele waarde die gelijk is aan 922337203685477, die groter is dan Int32.MaxValue (2147483647). Wat de cast in dit geval doet, is implementatiespecifiek (technisch is dit undefined zie de reactie van @ phoog hieronder) en zal waarschijnlijk afhangen van de CPU, wat de verschillen kan verklaren die u bent zien.

In één geval leidt de cast tot een waarde die acceptabel is voor System.Threading.WaitHandle.WaitOne (int) en in de andere gevallen niet.

Dit lijkt een fout te zijn in de bibliotheek die u gebruikt. Er is een WaitOne-overbelasting die een TimeSpan als argument gebruikt, dus ik weet niet waarom ze dat niet hebben gebruikt. Als je de bibliotheek niet kunt veranderen, heb je pech.

4
toegevoegd
Ja, het is een fout. Zeer irriterend.
toegevoegd de auteur Dustin Davis, de bron
@MarcGravell ook, in ECMA 334, is het niet "implementatiespecifiek" maar het is ongedefinieerd: in een ongecontroleerde context slaagt de conversie altijd en gaat als volgt verder. • De waarde is afgerond naar nul tot de dichtstbijzijnde integraalwaarde. Als deze integraalwaarde binnen het bereik van het doeltype valt, is deze waarde het resultaat van de conversie. • Anders is het resultaat van de conversie een niet-gespecificeerde waarde van het bestemmingstype.
toegevoegd de auteur phoog, de bron
@Marc Gravell ECMA 335, partitie III, 3.27 conv. - dataconversie "Als overflow optreedt waarbij een drijvende-komma-type wordt geconverteerd naar een geheel getal of als de drijvende-kommawaarde die wordt geconverteerd naar een geheel getal een NaN is, de geretourneerde waarde is niet opgegeven. "
toegevoegd de auteur phoog, de bron
@phoog aaahhh, TotalMilliseconds is double ! mijn fout; dan ja. Ik dacht eraan als long , in welk geval het gewoon de extra bits zou trimmen. Maar ja, voor dubbel is dat logisch.
toegevoegd de auteur Marc Gravell, de bron
Weet je zeker dat het implementatiespecifiek zou zijn? het gedrag voor andere bewerkingen (extra, vermenigvuldiging, enz.) is volledig gespecificeerd in overflow-condities (gecontroleerd versus niet aangevinkt) - Ik vind het vreemd dat de beperkende conversie niet gekwalificeerd zou zijn.
toegevoegd de auteur Marc Gravell, de bron

De enige manier waarop dit kan gebeuren met identieke VM's is als de CPU op de productiemachine verschillend is van de CPU in de ensceneringmachine - als Gabe gevraagd in de opmerkingen bij de vraag en zdan voorgesteld in zijn antwoord .

So specifically as to what is going on. For machines that support SSE2, the cvttsd2si instruction is used by .NET to convert the double into an int, where overflow is mapped to 0x80000000 (Int.MinValue). On machines w/o SSE2 support, I could only look at the Rotor sources, and in jithelpers.cpp, it simply casts the double to an int32 - which w/o SSE2 on VC10 C++, ends up returning the value in the lower 32 bits so the value passed to wait should be 1566804069 (0x5D638865) as you saw in the immediate window.

De CPU's zijn anders en je "fix" zonder de code aan te passen is om machines te veranderen in iets dat SSE2 niet ondersteunt. Zie de SSE2 wikipedia-invoer om de CPU van de productieserver te vergelijken met de staging-server. Als je geluk hebt, kan het misschien worden uitgeschakeld in de bios van je server (of de VMs config/bios).

Als je durft, kun je proberen de IL te patchen om het probleem op te lossen - wat de code echt wilde was -1 als de time-out, wat "wacht voor altijd" is. Door ilasm en ildasm te gebruiken, zou je het zonder bron kunnen repareren (ik neem aan dat dit de reden is dat je het niet kunt veranderen). Ik heb dit zelf succesvol gedaan in een testprogramma - ildasm test.exe /out=test.il om de assembly om te zetten in IL, de IL bewerkt en tenslotte ilasm test.il/exe om een ​​nieuwe assembly te maken. Hieronder ziet mijn IL eruit en hoe ik het heb opgelost.

// bad code
// var t = TimeSpan.MaxValue;
IL_0008:  call       instance float64System.TimeSpan::get_TotalMilliseconds()

// int x = (int)t.TotalMilliseconds;
IL_000D:  conv.i4   //This is the line that becomes cvttsd2si when jitted
IL_000E:  stloc.2

// wh.WaitOne(x);
IL_000F:  ldloc.0
IL_0010:  ldloc.2
IL_0011:  callvirt   instance bool System.Threading.WaitHandle::WaitOne(int32)

De oplossing is om x (locatie 2 hier) opnieuw te laden met -1 voordat je wacht Wacht een

// fixed code
// var t = TimeSpan.MaxValue;
IL_0008:  call       instance float64System.TimeSpan::get_TotalMilliseconds()

// int x = (int)t.TotalMilliseconds;
IL_000D:  conv.i4   //This is the line that becomes cvttsd2si when jitted
IL_000E:  stloc.2

// x = -1;//Fix by forcing x to -1 (infinite timeout)
          ldc.i4.m1 //push a -1
          stloc.2   //pop and store it in 'x'

// wh.WaitOne(x);
IL_000F:  ldloc.0
IL_0010:  ldloc.2
IL_0011:  callvirt   instance bool System.Threading.WaitHandle::WaitOne(int32)

Merk op dat in dit geval 'x' lokaal # 2 is - de IL bovenaan de methode geeft je de juiste # zodat de 2 in stloc.2 moet worden gewijzigd in wat # x is toegewezen, wat zou moeten overeenkomen met de # in de instructie ldloc net voor de oproep de WaitOne op label IL_0010 in mijn voorbeeld.

4
toegevoegd
Ik geef je het antwoord voor de moeite. Uiteindelijk heb ik 1) de bug gerapporteerd en ze zijn egoing om het te repareren en laat me weten wanneer een nieuwe versie beschikbaar is 2) Ik heb PostSharp gebruikt om een ​​aspect toe te passen dat de Milliseconds-eigenschap onderschept en ik verander het naar een geschikte waarde (Fancy way van doen wat je suggereert). Maar je hebt een geweldige oplossing geboden.
toegevoegd de auteur Dustin Davis, de bron

Uw conversie overstort dat waarom u verschillende resultaten krijgt voor verschillende systemen. Gebruik in plaats daarvan lang

  var t = TimeSpan.MaxValue;
  long x = (long)t.TotalMilliseconds;
0
toegevoegd
RAAK de vraag TERUG, deze bevindt zich in een API die we gebruiken, dus we kunnen deze niet wijzigen anders zou ik deze vraag niet stellen.
toegevoegd de auteur Dustin Davis, de bron
Nee, het juiste ding om te doen is gewoon WaitOne (-1) aan te roepen, maar het OP kan dat niet doen omdat hij alleen de controle heeft over de omgeving, niet de code .
toegevoegd de auteur Gabe, de bron

TimeSpan.MaxValue is gelijk aan Int64.MaxValue die een te grote waarde is om door te geven aan WaitOne (). Als u een grote waarde wilt doorgeven, gebruikt u gewoon Int32.MaxValue.

0
toegevoegd
Ik kan de code zien met ILSpy. wiki.sharpdevelop.net/ILSpy.ashx Ik verwacht niet dat ik de code kan repareren, ik ben op zoek om te weten waarom het werkt op twee systemen, maar niet op een ander.
toegevoegd de auteur Dustin Davis, de bron
Dit is geen code die ik niet kan veranderen, je antwoord is ongeldig.
toegevoegd de auteur Dustin Davis, de bron
Ik snap het niet, je hebt code die crasht, maar je kunt het niet veranderen? Hoe verwacht je dit te repareren?
toegevoegd de auteur Dylan Smith, de bron