Category Archives: Development

ClrMD: Nalezení statických rootů při analýze .NET memory-dumpu

Používáme-li WinDbg a jeho rozšíření SOS.dll (popř. SOSEX nebo PSSCOR4) k analýze memory-dumpu .NET procesů, pak jednou z komplikovanějších úloh je například nalezení statických rootů, tedy statických fieldů, které drží reference na prozkoumávanou instanci.

Že se jedná o zarootování instance ze statického fieldu je zpravidla zřejmé z výstupu !GCRoot, který pak vypadá například nějak takto:

0:000> !GCRoot 0000000acaf3e8f8 
HandleTable:
    0000000f159f19f8 (pinned handle)
    -> 0000000ec87f1be8 System.Object[]
    -> 0000000d11ac08c8 System.Action
    -> 00000010f5df1020 System.Object[]
    -> 0000000acaf3e8f8 System.Action

Found 1 unique roots (run '!GCRoot -all' to see all roots).

.NET si drží hodnoty statických fieldů v systémových object[] polích, nicméně dohledat, který konkrétní statický field je naším rootem, už není tak jednoduché (obvykle skončíme ve WinDbg u s-d nebo s-q prohledávání paměti a hledání další reference, atp.).

ClrMD: Microsoft.Diagnostics.Runtime

Naštěstí existuje a z NuGet se dá snadno nainstalovat .NET knihovna Microsoft.Diagnostics.Runtime (zkráceně ClrMD), která umožňuje inspekci a analýzu memory-dumpu z .NET kódu obdobně jako byste pracovali s SOS ve WinDbg.

…a jednou z věcí, které lze pomocí ClrMD snadno dostat je právě identifikace statického fieldu, který je rootovou referencí na prozkoumávaný objekt:

DataTarget dataTarget = DataTarget.LoadCrashDump(@"D:\path\to\memory-dump.DMP");
ClrRuntime runtime = dataTarget.CreateRuntime(@"D:\path\to\mscordacwks.dll");
ClrHeap heap = runtime.GetHeap();

foreach (ClrRoot root in heap.EnumerateRoots())
{
	if (root.Object == 0x0000000d11ac08c8) // adresa rootu, který chceme prozkoumat
	{
		// jen pro ukázku třeba:
		Console.WriteLine(root.Kind);
		if (root.Kind == GCRootKind.StaticVar)
			Console.WriteLine(root.Name);
	}
}

Pracujeme ideálně paralelně s WinDbg+SOS:

  • do CreateRuntime() dáváme cestu k příslušnému mscordacwks.dll, z WinDbg získáme přes .cordll -ve -u -l
  • adresu prozkoumávaného rootu známe z WinDbg (první po System.Object[], viz výše uvedený příklad z !GCRoot)
  • výše uvedený snippet stačí prsknout do ConsoleApp, do které nareferencujeme ClrMD z NuGet
        • je potřeba nastavit Build / Platform Target na x86 nebo x64, podle toho, z jaké platformy pochází memory-dump

Zkušenosti s přechodu na TFS a agilní techniky – záznam a slides [WUG Praha 1/2015]

Slides z přednášky 15.1.2015 pro Windows User Group Praha (WUG), kde jsme s kolegou Jirkou Kandou povídali i našich zkušenostech z přechodu HAVITu na TFS a agilní techniky (v období 08/2012 až 12/2014):

Z přednášky jsem pořizoval záznam, který najdete na našem HAVIT YouTube Channelu:

Omluvte sníženou kvalitu zvuku záznamu, nahrávali jsme tentokrát na dva různé mikrofony a výsledný záznam je sestřihán ze dvou různých zvukových stop (jedna z mikrofonů navíc nebyl zřejmě úplně ideálně umístěn a chytal nežádoucí ruchy).

Konverze časové zóny ve webové službě

Datový typ DateTime má v sobě položku DateTimeKind, která může nabývat hodnot Local, Utc, Unspecified. Podle nastavení této hodnoty je po-té provedena konverze časové zóny při volání webové služby.

V případě kindu unspecified se konverze časové zóny neprovádí. V případě kindu local se převede časová zóna volající strany do časové zóny příjemce. V případě kindu UTC se čas také nepřevádí.

Kind se při přenosu nemění.

Zde jsou příklady, jak která operace v C# nastavuje DateTimeKind:

DateTime dt1 = DateTime.Now; // DateTimeKind.Local
DateTime dt2 = DateTime.UtcNow; // DateTimeKind.Utc
DateTime dt3 = new DateTime(2014, 1, 1); // DateTimeKind.Unspecified
DateTime dt4 = DateTime.Now.ToLocalTime(); // DateTimeKind.Local
DateTime dt5 = DateTime.Now.ToUniversalTime(); // DateTimeKind.Utc
DateTime dt6 = DateTime.SpecifyKind(DateTime.Now.ToUniversalTime(), DateTimeKind.Unspecified); // DateTimeKind.Unspecified, pouze změna kindu bez konverze času

Při přenosu času jsme nakonec použili toto řešení:

Struktura:
...
public class WorkLogItemDTO
{
  /// <summary>
  /// Datum pracovního záznamu.
  /// </summary>
  public DateTime Datum { get; set; }

  /// <summary>
  /// Identifikátor klientské časové zóny.
  /// </summary>
  public string TimeZoneID { get; set; }
...
 
Klient (při volání serveru):
...
dto.Datum = item.Datum;
dto.TimeZoneID = TimeZoneInfo.Local.Id;
...

Server:
...
if (item.Datum.Kind != DateTimeKind.Unspecified)
{
  if (string.IsNullOrEmpty(item.TimeZoneID))
  {
    throw new Exception("Casova zona neni vyplnena (WorkLogItemDTO->IdTimeZone).");
  }
  TimeZoneInfo clientTimeZone = TimeZoneInfo.FindSystemTimeZoneById(item.TimeZoneID);
  item.Datum = TimeZoneInfo.ConvertTime(item.Datum, clientTimeZone);
}
...

 

Azure WebJob se občas spouští dvakrát

Problém

Máme v Azure nasazeno několik Azure Website spolu s naplanovanými (scheduled) web job. Řešíme situaci, kdy se webjob spustí dvakrát, zhruba v rozmezí jedné minuty. (V našem konkrétním případě posíláme notifikační emaily a tyto nám přijdou dva.)

Analýzou historie zjišťuji, že scheduler se webjob pokusil úlohu spustit dokonce třikrát.

webjob-history

Přičemž log jednotlivých pokusů vypadá takto:

  1. Http Action – Request to host ‚(…).scm.azurewebsites.net‘ failed: The timeout (30 seconds) was reached.
  2. Http Action – Response from host ‚(…).scm.azurewebsites.net‘: ‚Conflict‘ Response

    Body: Cannot start a new run since job is already running.
  3. Http Action – Response from host ‚(…).scm.azurewebsites.net‘: ‚Accepted‘ Response

Položky logu interpretuji takto:

  • Bod 1 úspěšně spustit webjob, ale nebyl schopen v intervalu 30 sekund toto zjistit.
  • Bod 2 se pokusil spustit webjob, ale ten již běžel, takže byl oznámen konflikt.
  • Bod 3 se opět pokusil spustit webjob, ten již doběhl, takže byl webjob úspěšně (ve skutečnosti podruhé) spuštěn.

Řešení

Po přečtení

se pokouším interpretovat (snad) klíčovou větu „If your site runs continuous web jobs, you should enable Always On, or the web jobs may not run reliably“ tak, že Always On zapínám i pro náš scheduled Web Job. Pomáhá to.

Volba Always On se nastavuje v Azure portálu, v nastavení Azure Website, se přepínačem Always On na záložce Configure.

Přístup k Team Foundation Serveru z Azure Website

Problém

V jedné z aplikací přistupujeme pomocí API jako klient do Team Foundation Serveru 2013.

ICredentials credentials = new NetworkCredential(...);
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(new Uri(...), credentials);
collection.EnsureAuthenticated();

Po migraci aplikace z vlastních serverů do Azure Website nám připojení přestalo fungovat, konkrétně začalo docházet k této výjimce:

System.IO.IOException: The specified registry key does not exist.
   at Microsoft.Win32.RegistryKey.Win32Error(Int32 errorCode, String str)
   at Microsoft.Win32.RegistryKey.CreateSubKeyInternal(String subkey, RegistryKeyPermissionCheck permissionCheck, Object registrySecurityObj, RegistryOptions registryOptions)
   at Microsoft.Win32.RegistryKey.CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistryOptions options)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.RegistryTokenStorageHelper.GetRootKey(String subkeyName)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.RegistryTokenStorage.RetrieveToken(VssTokenKey tokenKey)
   at Microsoft.VisualStudio.Services.Common.TokenStorage.VssTokenStorage.Retrieve(VssTokenKey tokenKey)
   at Microsoft.TeamFoundation.Client.TfsClientCredentialStorage.RetrieveToken(Uri serverUrl, VssCredentialsType credentialType)
   at Microsoft.TeamFoundation.Client.CookieCredential.OnCreateTokenProvider(Uri serverUrl, HttpWebResponse response)
   at Microsoft.TeamFoundation.Client.IssuedTokenCredential.CreateTokenProvider(Uri serverUrl, HttpWebResponse response, IssuedToken failedToken)
   at Microsoft.TeamFoundation.Client.TfsClientCredentials.TryGetTokenProvider(Uri serverUrl, IssuedTokenProvider&amp; provider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestHelpers.PrepareWebRequest(HttpWebRequest webRequest, Guid sessionId, String operationName, CultureInfo cultureInfo, TfsRequestSettings settings, TfsClientCredentials credentials, IdentityDescriptor impersonate, IssuedToken&amp; currentToken, IssuedTokenProvider&amp; tokenProvider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestHelpers.CreateSoapRequest(Uri requestUri, Guid sessionId, String soapAction, String operationName, CultureInfo cultureInfo, TfsRequestSettings settings, TfsClientCredentials credentials, IdentityDescriptor impersonate, IssuedToken&amp; currentToken, IssuedTokenProvider&amp; tokenProvider)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpWebRequest.CreateWebRequest()
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpWebRequest.SendRequest()
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpRequestChannel.Request(TfsMessage message, TimeSpan timeout)
   at Microsoft.TeamFoundation.Client.Channels.TfsHttpClientBase.Invoke(TfsClientOperation operation, Object[] parameters, TimeSpan timeout, Object[]&amp; outputs)
   at Microsoft.TeamFoundation.Framework.Client.Registration.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationProxy.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.RefreshMemoryCache()
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.RefreshCachesIfNeeded(Boolean direct)
   at Microsoft.TeamFoundation.Framework.Client.RegistrationService.GetRegistrationEntries(String toolId)
   at Microsoft.TeamFoundation.Framework.Client.PreFrameworkServerDataProvider.FindServiceLocation(String serviceType, String toolId)
   at Microsoft.TeamFoundation.Framework.Client.PreFrameworkServerDataProvider.LocationForCurrentConnection(String serviceType, Guid serviceIdentifier)
   at Microsoft.TeamFoundation.Client.TfsConnection.EnsureProviderConnected()
   at Microsoft.TeamFoundation.Client.TfsConnection.EnsureAuthenticated()

TFS klient v průběhu přihlašování šahá do registrů. Po dlouhém pátrání pomocí .NET Reflectoru jsem dospěl k názoru, že v nich hledá dříve uložený autentizační token. To pro nás není podstatné, důležité je, že se bez toho můžeme obejít.

Řešení

Řešením je použití třídy TfsClientCredentials. Tu vyrobíme pomocí WindowCredentials a tu s pomocí dvou tříd: NetworkCredentials a SimpleWebTokenCredential, přičemž v NetworkCredentials máme údaje k autorizaci a SimpleWebTokenCredential nemusí nést žádné hodnoty, pouze zajišťuje workaround popsaného problému.

NetworkCredential networkCredentials = new NetworkCredential(...)
TfsClientCredentials tfsCredentials = new TfsClientCredentials(new WindowsCredential(networkCredentials), new SimpleWebTokenCredential(null, null), allowInteractive: false);
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(new Uri(...), tfsCredentials);
collection.EnsureAuthenticated();

Azure Website: Pokus o stažení souboru *.woff souboru končí chybou 404, přestože existuje

Abych byl spravedlivý, nejde jen o Azure Website, ale i o Windows Servery 2008. Důvodem je nedefinovaný Content-Type (resp. MimeType) pro daný typ.

Ten můžeme přidat do web.configu:

<system.webServer>
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/x-woff" />
</staticContent>
</system.webServer>

Pokud však takovouto konfiguraci nasadíme na server, kde je již Content-Type (resp. MimeType) pro danou příponu nastaven, dojde k pádu aplikace. Zobrazená chyba nám oznámí, že se pokoušíme nastavit MimeType pro typ, který je již definován.

Pokud chceme mít jednu konfiguraci pro všechny serveru, ať již mají nebo nemají definovaný MimeType pro příponu woff, můžeme použít trik s remove:

<system.webServer>
<staticContent>
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/x-woff" />
</staticContent>
</system.webServer>

.NET Memory Internals 2/2 – Heap, Garbage Collector – záznam, slides a dema [MS Fest Praha 2014]

Slides z mé přednášky 30.11.2014 pro konferenci MS Fest Praha:

Z přednášky jsem pořizoval záznam, který najdete na našem HAVIT YouTube Channelu:

.NET Memory Internals 1/2 – záznam, slides a dema [MS Fest Praha 2014]

Slides z mé přednášky 29.11.2014 pro konferenci MS Fest Praha:

Z přednášky jsem pořizoval záznam, který najdete na našem HAVIT YouTube Channelu:

Azure: Nedaří se vytvořit databázi importem z *.bacpac

Při pokusu o založení nové databáze importem z *.bacpac v portálu se zobrazuje potvrzení, že přístup do Azure Storage je v pořádku a toto oznámení je následováno chybou oznamující, že nelze vložit požadavek na import databáze.

Heslo k databázovému serveru obsahovalo diakritiku, nápad zkusit heslo bez diakritiky se osvědčil –  po změně hesla funguje import bez problémů.

Na Azure mi nešla založit DB ve WestEurope, SQL server ano, jiné regiony ano (Azure Free Trial?)

Krátká empirická zkušenost.

Symptoms

Nová subscription, která vznikla tak nějak mimoděk, jako první subscription k danému organizational accountu a při zakládání této první subscription se to na nic neptalo a založilo to Trial Subscription s kreditem 150 EUR (pěkně děkuji, viz níže).

No nic, víc jsem to nezkoumal a prostě jen vypnul spending limit s tím, že se z toho stane běžná Pay-As-You-Go (

Zakládáme do subscription nové služby. WebSites v pohodě, WebJobs v pohodě, SendGrid v pohodě, při zakládání nové databáze „starý“ portal (manage.windowsazure.com) založí databázový server, ale k tomu řekne Could not create database ‚XY‘:

clip_image002

V detailech zprávy je výstižné upřesnění: Could not create database ‚XY‘.

Nejpozoruhodnější na tom je, že úplně stejná databáze v regionu East Asia se bez problémů vytvoří. Ostatní služby WestEurope OK.

Při použití nového portálu (portal.azure.com) je chování stejné, jen v detailech problému je cosi o „not supported for your subscription“ (WTF!). Na homepage všechny uzly Azure zelené, žádný výpadek není hlášen.

?Resolution?

Běžné Googlení „Could not create database“ dává diskuzní příspěvky o tom, že se něco v Azure nezpropagovalo a nezbývá než kontaktovat Azure Support. Pay-As-You-Go však má support omezený a nechce se mi čekat, než se to někam pohne. Zakládám tedy raději novou druhou subscription (tentokrát se to již ptá na subscription type a volím explicitně Pay-As-You-Go) a po chvilce usazování (napoprvé to opět nešlo, ale pro změnu nešel založit ani SQL server) vše úspěšně funguje.

?Cause?

Pokud i nyní zkusím na té prvně vytvořené subscription (která se pořád na různých místech identifikuje jako Trial a zřejmě tedy odstraněním spending limitu nedojde k přepnutí na produkční) založit databázi do WestEurope, dojde pouze k založení DB serveru, ale založení DB je zamítnuto.

Celé to zavání nějakým nedostatkem instancí v datovém centru WestEurope, což by vysvětlovalo i skutečnost, že subscription, která se označuje jako Trial, je na tom hůře, než následně založená Production. Divné jenom je, že na té Trial subscription to založí server a „jenom“ na něm nedovolí založit DB (i když i to nějakou logiku má).

Závěr

Přesnou příčinu neznám a více času s tím ztrácet nechci. Celé to do KnowledgeBase zapisuji proto, že nemusím být sám, kdo bude takovým „Free Trial“ obšťastněn.

Někdo máte podobnou/jinou zkušenost? Někdo máte lepší vysvětlení?