/* Drop all non-system stored procs */
DECLARE @name VARCHAR(128)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'P' AND category = 0 ORDER BY [name])
WHILE @name is not null
BEGIN
SELECT @SQL = 'DROP PROCEDURE [dbo].[' + RTRIM(@name) +']'
EXEC (@SQL)
PRINT 'Dropped Procedure: ' + @name
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'P' AND category = 0 AND [name] > @name ORDER BY [name])
END
GO
/* Drop all views */
DECLARE @name VARCHAR(128)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'V' AND category = 0 ORDER BY [name])
WHILE @name IS NOT NULL
BEGIN
SELECT @SQL = 'DROP VIEW [dbo].[' + RTRIM(@name) +']'
EXEC (@SQL)
PRINT 'Dropped View: ' + @name
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'V' AND category = 0 AND [name] > @name ORDER BY [name])
END
GO
/* Drop all functions */
DECLARE @name VARCHAR(128)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] IN (N'FN', N'IF', N'TF', N'FS', N'FT') AND category = 0 ORDER BY [name])
WHILE @name IS NOT NULL
BEGIN
SELECT @SQL = 'DROP FUNCTION [dbo].[' + RTRIM(@name) +']'
EXEC (@SQL)
PRINT 'Dropped Function: ' + @name
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] IN (N'FN', N'IF', N'TF', N'FS', N'FT') AND category = 0 AND [name] > @name ORDER BY [name])
END
GO
/* Drop all Foreign Key constraints */
DECLARE @name VARCHAR(128)
DECLARE @constraint VARCHAR(254)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY TABLE_NAME)
WHILE @name is not null
BEGIN
SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME)
WHILE @constraint IS NOT NULL
BEGIN
SELECT @SQL = 'ALTER TABLE [dbo].[' + RTRIM(@name) +'] DROP CONSTRAINT [' + RTRIM(@constraint) +']'
EXEC (@SQL)
PRINT 'Dropped FK Constraint: ' + @constraint + ' on ' + @name
SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' AND CONSTRAINT_NAME <> @constraint AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME)
END
SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'FOREIGN KEY' ORDER BY TABLE_NAME)
END
GO
/* Drop all Primary Key constraints */
DECLARE @name VARCHAR(128)
DECLARE @constraint VARCHAR(254)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' ORDER BY TABLE_NAME)
WHILE @name IS NOT NULL
BEGIN
SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME)
WHILE @constraint is not null
BEGIN
SELECT @SQL = 'ALTER TABLE [dbo].[' + RTRIM(@name) +'] DROP CONSTRAINT [' + RTRIM(@constraint)+']'
EXEC (@SQL)
PRINT 'Dropped PK Constraint: ' + @constraint + ' on ' + @name
SELECT @constraint = (SELECT TOP 1 CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' AND CONSTRAINT_NAME <> @constraint AND TABLE_NAME = @name ORDER BY CONSTRAINT_NAME)
END
SELECT @name = (SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE constraint_catalog=DB_NAME() AND CONSTRAINT_TYPE = 'PRIMARY KEY' ORDER BY TABLE_NAME)
END
GO
/* Drop all tables */
DECLARE @name VARCHAR(128)
DECLARE @SQL VARCHAR(254)
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'U' AND category = 0 ORDER BY [name])
WHILE @name IS NOT NULL
BEGIN
SELECT @SQL = 'DROP TABLE [dbo].[' + RTRIM(@name) +']'
EXEC (@SQL)
PRINT 'Dropped Table: ' + @name
SELECT @name = (SELECT TOP 1 [name] FROM sysobjects WHERE [type] = 'U' AND category = 0 AND [name] > @name ORDER BY [name])
END
GO
Author Archives: Robert Haken
T-SQL: Seznam tabulek bez IDENTITY
SELECT [schema] = s.name,
[table] = t.name
FROM sys.schemas AS s
INNER JOIN sys.tables AS t ON s.[schema_id] = t.[schema_id]
WHERE NOT EXISTS (SELECT 1
FROM sys.identity_columns
WHERE [object_id] = t.[object_id])
ORDER BY t.name
Vybírám týmový password manager – poznámky z průzkumu
V tomto postu si chci postupně zaznamenávat poznámky z hledání. Průběžně ho budu doplňovat, dokud nedojdeme k závěru.
Požadavky
Pro náš vývojářský tým hledám password manager, který by nám umožnil sdílet hesla (přístupy k zákazníkům, přístupy k zásobovacím službám, atp.).
- důvěryhodná služba
- cloudová služba (nechci nic instalovat ani udržovat), webové UI
- nastavování přístupových práv podle skupin
- emergency hesla (auditovaný jednorázový přístup k běžně nesdílenému administrátoskému heslu v případě krize) možnost využití i jako osobního password manageru (nice to have)
- mobilní aplikace (nice to have)
- browser extension (nice to have)
- rozumná cena (limit dejme tomu do $100/měsíc za 25-členný tým)
Prvotní průzkum – hodné dalšího zkoumání
Dashlane for Business
- https://www.dashlane.com/business
- Windows, Mac, iOS (vč. fingerprint) and Android + Internet Explorer, Chrome, Firefox and Safari browsers
- soukromá i týmová hesla (samostatné zóny), form-filling
- emergency contact (i na jednotlivá hesla/kategorii, i bez waiting period, nebo pak od 1 dne výš)
- nemá webové rozhraní, pouze lokální klienty, do kterých musí být hesla stažena (nutná instalace), i když koukám ne-teamové Premium to má, tak těžko říct
- dvoufaktorová authentizace (i pro “authorize new device”)
- Business Intro – https://www.youtube.com/watch?v=OzYEOlfVu68
- Business Deep-Dive (20min) – https://www.youtube.com/watch?v=aD9rawpTJgs
- sdílení je pouze po záznamech nebo celých kategoriích, záznamy bude asi obtížné v tom množství nějak organizovat bez složek; úroveň sdílení je pouze login/full
- neumím si představit nějakou centrální správu, asi je to uchoditelné v malém týmu
- $2 / user / month (30-day trial)
1Password Team
- aplikace Mac (původ), iOS, Android (Windows jen beta, Windows Phone není)
- extensions pro Chrome, FF, Operu, Safari, ale ne IE a EDGE
- webové rozhraní
- “mature” platforma
- Emergency Access lze jen přes jakési “sám se zařadím do týmu”
- oddělené trezory pro soukromá, týmová a další hesla
- skupiny uživatelů a logování přístupů jen drahá Pro verze !!
- Doporučuje Igor Kulman, byť nevyužívá týmově
- $4 / user / month – Standard, $12 Pro – do 15/OCT life-time cena za standard (30-day trial)
LastPass Enterprise
- https://lastpass.com/, https://enterprise.lastpass.com/
- “mature” aplikace pro single-user, rozšířena o shared folders a admin console
- mraky aplikací pro všechny platformy
- dle videí vypadá feature-rich a hodně enterprise (o to méně user-friendly !!! takové technicistní)
- záznamy se sdílejí po složkách, do složky se dává přístup skupině uživatelů, nebo jednotlivému uživateli
- oddělení soukromých hesel přes Linked Account
- Enterprise edice nemá Emergency Access, na rozdíl od Free/Premium !!
- dvoufaktorová authentizace; Windows Login / AD Integration
- má integrační REST API; SAML integrovatelnost = SSO pro další aplikace (bez potřeby autofill)
- Screencasty o Enterprise: https://www.youtube.com/playlist?list=PLsXN_jRonzyctRhVdPUKGurKUArMEWcIM
- Velké video: https://www.youtube.com/watch?v=vH6vSP4kphg&list=PLsXN_jRonzyctRhVdPUKGurKUArMEWcIM&index=9
- Aktuální UI: https://www.youtube.com/watch?v=oa9apKp_mHs&list=PLsXN_jRonzyctRhVdPUKGurKUArMEWcIM&index=14
- $2 / user / month (resp. $24/y), 14-day trial
RoboForm Enterprise (RoboForm Everywhere for Business)
- http://enterprise.roboform.com/, http://www.roboform.com/tutorials
- aplikace pro všechny paltformy vč. Windows Phone (vč. češtiny)
- “mature” platforma
- zřejmě nemá emergency access
- vlastní srovnání s ostatními http://enterprise.roboform.com/product-comparison/roboform-vs-competitors
- Doporučuje Tomáš Herceg, byť nevyužívá týmově
- $2.50 / user / month (resp. $30/y)
Vyřazené
- TeamPassword – https://www.teampassword.com, $100/m, nemá mobilní aplikace, vypadá chudě, ale asi by se dal
- TeamPasswordManager – http://teampasswordmanager.com/, on-premisse MySQL/PHP, pouze webová aplikace
- CommonKey – https://commonkey.com, pouze webová aplikace + chrome extension, funkčně chudé, $2/m
- KeePass – http://keepass.info/, pouze lokální aplikace offline
- SplashID Safe for Teams – používám jako osobní password manager už roky a je čas na změnu (horší UX, atp.)
- PasswordWallet – http://www.selznick.com/products/passwordwallet/ – nevypadá moc důvěryhodně, asi nemá tým
Nějaké tipy?
Lenovo X1 Carbon 4 vs. T440s – první dojmy po 10 dnech
I přes původní odhodlání setrvat se svým Lenovo ThinkPad T440s jsem nakonec podlehl příležitosti a upgradoval na Lenoxo X1 Carbon 4 (konkrétně model 20FC0038MC, tedy i7 6600U, 2560×1440 IPS, RAM 16 GB, SSD 512 GB, 4G LTE).
- Nižší zdvih klávesnice (nicméně identické rozložení). Nepozoruji rozdíl v pohodlí/přesnosti psaní.
- Fingerprint reader, na který stačí prst přiložit (ála iPhone) = rychlejší přihlášení.
- Vyšší rozlišení 2560×1440 (scale 150%) zatím vypadá, že bude příjemnější než FullHD (scale 125%). Při scale 150% se toho na obrazovku vejde o malinko více, a pro “sedím za stolem” je to přesně akorát. Pro “na klíně” je to na hraně, nejspíš bude potřeba přepínat na 175%.
- microSD card reader je víceméně symbolický, spíše pro verze s menším diskem jako cesta k získání další kapacity, než pro častější stahování dat z foťáku/GPS/… Slot je totiž poměrně obtížně přístupný (zezadu, notebook je potřeba prakticky zavřít)
- Menší touchpad (díky HW tlačítkům). Tlačítka sice nepoužívám, takže se mi zmenšuje využitelná plocha, nicméně jinak touchpad působí příjemněji – při “prokliku” nedělá takový nepříjemný hlučný cvak, jako T440s, spíš je to mělké kliknutí ála Macbook.
- Šasi působí stejně bytelně jako T440s, i když zde bude nejspíš ušlechtilejší materiál (carbon). Na první pohled není zřetelný rozdíl.
- Absence VGA D-SUB portu je vynahrazena plným HDMI (miniDP zůstává). Objednal jsem si jako nutnost HDMI-to-VGA redukci, nicméně jenom praxe ukáže, jak často bude potřeba.
- Absence RJ-45 je suplována přiloženým OneLink+ adaptérem. V praxi RJ-45 využívám řídce a problém to snad nebude.
- Hmotnost kolem 1,2kg je hodně příjemná. Denně vozím notebook do práce a z práce v batohu na kole, takže každé deko dolů se hodí.
- Windowsí indikátor baterie hlásí při plném nabití výdrž něco přes 7hod, u nové T440s to bylo tuším něco přes 6 hodin. Pro výměnu baterie je potřeba demontáž šasi.
- Notebook (zejm. Visual Studio coding) je subjektivně svižnější, částečně to samozřejmě může být způsobeno čistou instalací. Procesor je papírově rychlejší o cca 15%.
- SSD se identifikuje jako SanDisk SD7SN6S512G1001X, v Samsung Magicianu točí cca 440 MB/s read a 340 MB/s write. V T440s byl nějaký Samsung, který točí 510 MB/s read a tuším 200 MB/s write.
- Hlučnost (při zátěži) je subjektivně stejná, X1 nemá výdechy do boku, ale jen dolů, takže bude asi více topit do stehen a položený na stole bude asi horší odvod tepla. Zajímavé je, že “područky”, resp. místa vedle touchpadu, kde se dotýkají zápěstí, jsou minimálně na první dojem subjektivně zřetelně chladnější, než T440s. Přesněji řečeno jsou z počátku vyloženě studená, což by mohlo být důsledkem různého materiálu s jinou tepelnou vodivostí.
- Mobile broadband vypadá velmi slibně. Na LTE mi tu na horách točí 34/30 Mbps, což na T440s s “pouhým” 3G nehrozilo.
- WiFi se zdá mít lepší příjem než T440s. Pokud pracuji na zahradě, WiFi konektivita je použitelná, zatímco s T440s jsem na stejném místě byl už na hraně funkčnosti.
- Napájecí adaptér má X1 o maličko větší (65W) oproti T440s (tuším 45W). Na rozdíl od T440s je u něj měkčí a trochu subtilnější napájecí kabel. Naštěstí ho sebou nosím spíše výjimečně.
- Reproduktorky jsou více než symbolické. T440s dokázala poměrně nečekaně dobře zvučit, X1 je chrastítko pro smích. Používám naprosto minimálně a netrápí mě to, ale je to zřetelné na první přehrání.
- Dock OneLink+ je příjemný, na stole tolik nepřekáží a manipuluje se s ním snadno. Oproti klasickému pokládacímu docku je to takový hub připojený kabelem (integruje i napájení). Řeším jen dilema, jestli nechávat notebook v připojení na dock zavřený, ale chybí mi pak pohodlí čtečky otisků prstů, která zůstane nepřístupná.
Po 10 dnech tedy zatím nelituji, i když zásadní rozdíl oproti T440s to není.
MSDeploy Error: Unexpected end of file occured. The following elements are not closed: results.
Stává se mi to při nasazování webových aplikací s MSDeploy opakovaně a vždy mi chvíli trvá, než mi dojde, čím to je:

…na vině je jinak bezvadný Fiddler. Pokud ho vypnu, vše začne fungovat.
Azure 4-mins Idle Connection Timeout a jak se s ním vypořádat
Jedna z našich aplikacích migrovaných do Azure začala slavit neúspěchy při volání externí SOAP webové služby – System.Net.WebException: The operation has timed out at… Konkrétně se jednalo o WebJob pro synchronizaci master-dat s ERP systémem zákazníka a pozoruhodné na tom bylo, že z mnoha volaných služeb ERP padala jedna jediná.
Záhy se navíc ukázalo, že volání služby padá pouze z prostředí Azure, volání stejné služby z vývojářského i našeho původního on-premise produkčního prostředí fungovalo. Potvrdilo se to nejenom úspěšným voláním stejné služby z non-Azure prostředí, ale i neúspěšným voláním stejné služby z virtual machine založené pro účely testu v Azure (pro toto se výborně hodí Azure kredit, který je součástí každého MSDN předplatného).
Po chvilce zamyšlení a další chvilce Googlení, se potvrdila hypotéza, že Azure uplatňuje na síťovou komunikaci určité restrikce, zde konkrétně 4-min connection idle timeout a volané webové službě trvalo 5 minut, než nám data z ERP naservírovala. Limit se projevuje a nejenom u příchozích spojení, ale i u spojení odchozích a je potřeba se s ním vypořádat.
Zatímco u příchozích spojení se dá limit konfigurovat na Load Balanceru, u odchozích spojení se předpokládá, že se s ním vypořádáte sami. Principiálně jde totiž o to, že je potřebovat udržovat nějakou aktivitu spojení.
Řešení se nabízí pro různé situace mnoho (např. připravené WCF Azure Net.TCP Keep Alive – net.tcp služba, která periodicky posílá keep-alive signál), v zásadě však většinou není potřeba sahat k ničemu komplikovanému a stačí využít vestavěný keep-alive TCP, který lze v .NET konfigurovat přes ServicePoint.SetTcpKeepAlive(..), resp. ServicePointManager.
Buď můžeme keep-alive konfigurovat globálně pro všechna TCP spojení:
ServicePointManager.SetTcpKeepAlive(true, 5000, 5000);
Nebo nastavení omezit na konkrétní protistranu:
var servicePoint = System.Net.ServicePointManager.FindServicePoint(new Uri(targetWebServiceUrl)); servicePoint.SetTcpKeepAlive(true, 30000, 30000);
Python (update 09/2018)
Pokud máte projekt v Python, pak meducína vypadá takto (Credits datasentics.com):
import socket from requests.packages.urllib3.connection import HTTPConnection HTTPConnection.default_socket_options.append((socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1))
Instalace SQL Server 2016 (Express) LocalDB
SQL Server 2016 RTM, na rozdíl třeba od své RC0, již ve svém hlavním instalátoru nenabízí check-box na instalaci LocalDB, přesněji řečeno “někdy nabízí, někdy nenabízí”. Zřejmě podle toho, jestli už na stroji něco nachází, nebo ne. Každopádně doinstalaci provedete samostatným instalátorem, který je na SQL Server DVD:
<dvd>:\1033_ENU_LP\x64\Setup\x64\SqlLocalDB.msi
PS: Při instalaci SQL Serveru 2016 Express by měl být checkbox LocalDB ve fázi Feature Selection, ale ani na Expressu se mi nenabízel.
Debugging Story: Z Azure WebJobs dashboardu nelze spustit funkci s DateTime parametrem
V krátkosti se podělím o svůj poslední debugging zážitek – ani ne proto, že by byl výjimečný svým zjištěním, ale spíše jako inspiraci pro techniky a postupy, které lze při debuggingu použít.
Symptom
Problém se projevoval tím, že z Azure WebJobs Dashboardu (Kudu) se nám nedařilo spouštět funkci, které jsme potřebovali jako vstupní parametr předat hodnotu typu DateTime:

Spuštění končilo chybou System.FormatException: String was not recognized as a valid DateTime:
![clip_image002[5] clip_image002[5]](https://knowledge-base.havit.cz/wp-content/uploads/2016/06/clip_image0025.jpg?w=738&h=768)
Architektura řešení
WebJobs SDK umožňuje, abyste vytvořili WebJob, který v sobě má funkce (Functions). Tyto mohou být spouštěny podle časového plánu, triggerovány událostí na straně Azure (např. nová message v Queue, nový soubor na disku, nový záznam v DB tabulce, atp.), nebo spouštěny ručně pomocí UI Azure WebJobs Dashboardu (součást Kudu, resp. jeho automatické rozšíření).
V našem případě se jednalo o ruční spouštění funkce z Azure WebJobs Dashboardu, které na pozadí funguje tak, že s WebJobs hostem (vaším procesem – WebJobem – který v sobě má aktivní JobHost z WebJobs SDK) komunikuje prostřednictvím Azure Queue.
1. Kontrola formátu data na vstupu
Na začátku člověk samozřejmě pochybuje o tom nejjednodušším – tedy jestli zadává do UI WebJobs Dashboardu hodnotu ve správném formátu. Je to samozřejmě webový formulář, který musí string zapsaný do textboxu nějak parsovat na DateTime.
Hledal jsem tedy ve zdrojácích WebJob SDK (GitHub), jak se vstup na DateTime převádí. Protože už jsem sám implementoval binding vlastního typu (aby bylo možno pohodlně volat z UI funkci, která potřebuje typ na vstupu), celkem rychle jsem se donavigoval na StringToDateTimeConverter, ve kterém je
public DateTime Convert(string input)
{
return DateTime.ParseExact(input, "O", CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
}
Aby si ověřil, že mám správnou podobu vstupu, použil jsem nový C# Interactive z Visual Studia 2015 (stejně snadno by pomohl i LINQPad):

…zde tedy zakopaný pes nebude, vstup máme správně.
2. Zjištění, jaký string vstupuje do konverze DateTime.ParseExact
Metoda DateTime.ParseExact(..) je poměrně přísná na vstupní formát. Vadí jí i trailing whitespace a podobné “drobnosti”, tedy jsem se vydal cestou zjišťování, co do této metody vstupuje, a kde se to po cestě “pokazí”.
Bohužel situace není úplně jednoduchá – WebJobs SDK a jeho komunikace s Dashboardem nám zde běží v prostředí Azure a není úplně triviální vše napojit z lokálního prostředí a debugovat klasicky ve Visual Studiu (byť by asi nebyl problém lokální instanci nasměrovat na příslušný Azure Storage Account). Nechtělo se mi řešit tuto cestu, která by mi stejně umožnila ladění jen na straně WebJobs hosta, vydal jsem přes analýzu produkčního prostředí Azure.
Mým prvním cílem bylo získat memory-dump WebJobs hosta a zkusit v něm najít, jaký string se parsuje. Vzhledem k tomu, že můj WebJobs host nebyl v danou chvíli zatížen jinou aktivitou, nezdržoval jsem se nastavováním podmínek, za kterých chci memory-dump sejmout (např. při výjimce FormatException, nebo bych mohl zvolit breakpoint do metody Convert, atp.). Rozhodl jsem se to risknout, použít prostý ručně sejmutý memory-dump a doufat, že Garbage Collector mi příslušné instance ještě z paměti nevyhodil, a že v něm něco najdu.
Pro prohledávání memory-dumpu se výborně hodí NetExt rozšíření WinDbg se svým !wfrom:
0:000> .symfix 0:000> .reload 0:000> .load c:\Windows\Microsoft.NET\Framework\v4.0.30319\sos 0:000> .load netext 0:000> !wfrom -type System.String where ( $contains($string(),"2016-05-14") ) select $addr(), $string() 0 Object(s) listed 71,537 Object(s) skipped by filter
…nicméně žádný řetězec, který by v sobě měl “2016-05-14” se v paměti nenašel.
Nenechal jsem se odradit (byť už jsem podezíral GC) a zkusil hledat jen “2016”:
0:000> !wfrom -type System.String where ( $contains($string(),"2016") ) select $addr(), $string() .. calculated: 05/14/2016 15:05:23 +02:00 calculated: 0737EC64 calculated: 05/14/2016 15:05:23 +02:00 calculated: 073C1E58 ... 50 Object(s) listed 71,487 Object(s) skipped by filter
…padesát stringů s “2016” nalezeno (výpis zkrácen) a mezi nimi i dva, které mě velmi zajímaly. Jsou to přesně moje vstupní hodnoty, jenom jinak formátovány. Evidentně se jedná o stringy, které se DateTime.ParseExact(…) snažil neúspěšně parsovat.
3. Zjišťování, kde se stringová reprezentace data a času změní
V tuto chvíli již bylo více než pravděpodobné, že se jedná o bug ve WebJobs SDK, nicméně mi to nedalo, abych se v tom nepovrtal dále. Chtěl jsem co nejlépe lokalizovat místo, kde dojde k poničení stringu s datem.
Procházel jsem tedy ve zdrojových kóde WebJobs SDK cestu, kterou tečou argumenty od UI Dashboardu (FunctionController.cs – GetArguments(), Invoke()), přes odesílání zprávy do Azure Queue (HostMessaging/Invoker.cs) a dále.
Protože jsem měl k dispozici memory dump WebJobs Hosta, který z Azure Queue naopak zprávy vyzvedává a vykonává, chtěl jsem zkontrolovat, jakou zprávu z queue vyzvedne a jestli už v ní jsou argumenty poškozeny. Ze zdrojových kódů je zřejmé, že komunikace proběhne zasláním zprávy typu CallAndOverrideMessage, která v sobě má IDictionary<string, string> argumentů. Použil jsem tedy !DumpHeap s filtrem na typ a následně i !wdict z NetExt pro přehledné vypsání obsahu Dictionary:
0:000> !DumpHeap -type CallAndOverrideMessage
Address MT Size
01e17eb4 060223fc 60
01e18448 06073050 56
07380594 06073050 56
073c2d78 060223fc 60
Statistics:
...
0:000> !do 01e17eb4
Name: Microsoft.Azure.WebJobs.Host.Protocols.CallAndOverrideMessage
MethodTable: 060223fc
EEClass: 06018c14
Size: 60(0x3c) bytes
File: D:\local\Temp\jobs\continuous\JobScheduler\3pfsjnlr.4ry\Microsoft.Azure.WebJobs.Host.dll
Fields:
MT Field Offset Type VT Attr Value Name
72cbd834 4000001 4 System.String 0 instance 01e17d4c <Type>k__BackingField
72cba91c 4000018 14 System.Guid 1 instance 01e17ec8 <Id>k__BackingField
72cbd834 4000019 8 System.String 0 instance 01e17d78 <FunctionId>k__BackingField
7289231c 400001a c ...tring, mscorlib]] 0 instance 01e17ef0 <Arguments>k__BackingField
06022340 400001b 10 System.Int32 1 instance 2 <Reason>k__BackingField
72c6cb20 400001c 24 ....Guid, mscorlib]] 1 instance 01e17ed8 <ParentId>k__BackingField
0:000> !wdict 01e17ef0
Items : 3
[0]:==============================================(Physical Index: 2)
System.__Canon key = 01e17e98 dateTo
System.__Canon value = 01e1816c 05/14/2016 15:05:23 +02:00
[1]:==============================================(Physical Index: 0)
System.__Canon key = 01e17e34 businessKind
System.__Canon value = 01e17e5c Onshore
[2]:==============================================(Physical Index: 1)
System.__Canon key = 01e17e78 dateFrom
System.__Canon value = 01e17f80 05/14/2016 15:05:23 +02:00
Zde se ukázalo, že příchozí zpráva z Azure Queue, resp. její deserializace už v sobě chybný změněný formát data má.
Nedalo mi to a hledal jsem, co přesně teče přes Azure Queue. Chvilku jsem se marně snažil zachytit zprávu ve Visual Studiu přes Cloud Explorer, až jsem se nakonec vrátil ke zdrojovým kódům WebJobs SDK a jejich implementaci příjmu zpráv z Azure Queue přes StorageQueueMessage, která v sobě (resp. přes CloudQueueMessage z Azure Storage) drží raw-stringovou reprezentaci zprávy:
0:000> !DumpHeap -type CloudQueueMessage /d
Address MT Size
0134bae0 057d7fa0 12
01404e24 05b30b38 12
01404e30 05b31034 12
01459908 04ada834 32
01459e2c 04add3c0 12
01459e90 04adac64 32
01e17b34 05b30e58 80
0734bd5c 05b30e58 80
07473d64 05b30e58 80
Statistics:
...
0:000> !DumpObj /d 07473d64
Name: Microsoft.WindowsAzure.Storage.Queue.CloudQueueMessage
MethodTable: 05b30e58
EEClass: 057c4180
Size: 80(0x50) bytes
File: D:\local\Temp\jobs\continuous\JobScheduler\3pfsjnlr.4ry\Microsoft.WindowsAzure.Storage.dll
Fields:
MT Field Offset Type VT Attr Value Name
72cbd834 4000141 4 System.String 0 instance 07473828 <Id>k__BackingField
72cbd834 4000142 8 System.String 0 instance 07473880 <PopReceipt>k__BackingField
72c696f4 4000143 1c ...ffset, mscorlib]] 1 instance 07473d80 <InsertionTime>k__BackingField
72c696f4 4000144 2c ...ffset, mscorlib]] 1 instance 07473d90 <ExpirationTime>k__BackingField
72c696f4 4000145 3c ...ffset, mscorlib]] 1 instance 07473da0 <NextVisibleTime>k__BackingField
72cbf6bc 4000146 14 System.Int32 1 instance 1 <DequeueCount>k__BackingField
05b30da4 4000147 18 System.Int32 1 instance 1 <MessageType>k__BackingField
72cbd834 4000148 c System.String 0 instance 074738c8 <RawString>k__BackingField
72cc2518 4000149 10 System.Byte[] 0 instance 00000000 <RawBytes>k__BackingField
72cbab18 400013f 150 System.TimeSpan 1 static 021f4f28 MaximumTimeToLive
72cba5a0 4000140 154 ...Text.UTF8Encoding 0 static 0734bfb4 utf8Encoder
0:000> !DumpObj /d 074738c8
Name: System.String
MethodTable: 72cbd834
EEClass: 728ff344
Size: 1166(0x48e) bytes
File: D:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: ew0KICAiVHlwZSI6ICJDYWxsQW5kT3ZlcnJpZGUiLA0KICAiSWQiOiAiMmExYzYwNjMtNmFkZC00MmE1LWE1M2EtNjZhYmM2MTY3NmI5IiwNCiAgIkZ1bmN0aW9uSWQiOiAiSGF2aXQuWGVyb3hXZWJUb29sLkpvYlNjaGVkdWxlci5Kb2JzLkVycFN5bmMuUXVlcmllc0Rvd25sb2FkRXJwU3luY0pvYi5FeGVjdXRlRGF0ZVJhbmdlIiwNCiAgIkFyZ3VtZW50cyI6IHsNCiAgICAiYnVzaW5lc3NLaW5kIjogIk9uc2hvcmUiLA0KICAgICJkYXRlRnJvbSI6ICIyMDE2LTA1LTE0VDE1OjA1OjIzLjk4ODA4MTQrMDI6MDAiLA0KICAgICJkYXRlVG8iOiAiMjAxNi0wNS0xNFQxNTowNToyMy45ODgwODE0KzAyOjAwIg0KICB9LA0KICAiUmVhc29uIjogIkRhc2hib2FyZCIsDQogICJQYXJlbnRJZCI6ICJiYjQyYWYwYS00MTcyLTRiYjUtYTRiMS01ZThiMzQwMDU1MDkiDQp9
Fields:
MT Field Offset Type VT Attr Value Name
72cbf6bc 4000243 4 System.Int32 1 instance 576 m_stringLength
72cbe278 4000244 8 System.Char 1 instance 65 m_firstChar
72cbd834 4000248 40 System.String 0 shared static Empty
>> Domain:Value 0095f5d0:NotInit <<
Ze zdrojových kódů Azure Storage CloudQueueMessage.AsString je zřejmé, že v RawString je base64, který snadno převedeme pomocí https://www.base64decode.org/:

…zde se překvapivě ukazuje, že JSON serializace zprávy má v sobě korektní (původní) podobu data, a že problém tedy je v konverzi z CloudQueueMessage, resp. StorageQueueMessage na CallAndOverrideMessage. Ve zdrojácích už nebyl problém dohledat, že se tak děje v HostMessageExecutor pomocí JsonConvert deserializace:
HostMessage model = JsonConvert.DeserializeObject<HostMessage>(value.AsString, JsonSerialization.Settings); CallAndOverrideMessage callAndOverrideModel = model as CallAndOverrideMessage;
4. Neumí tedy Newtonsoft Json.NET správně deserializovat string?
WebJobs SDK používá pro HostMessage customizovaný JsonConvertor, při jehož použití se poškození stringu s datem projeví. Pokud se convertor odebere a ponechá se výchozí serializace+deserializace, problém se neprojeví.
Více do hloubky už jsem nešel, protože písmenko “J” na začátku názvu vývojářské technologie je pro mě bránou do pekla, kterou jsem nechtěl projít. Ke problému jsem vytvořil jednoduché repro a nareportoval issue WebJobs SDK týmu.
Azure Web Apps: Získání memory-dumpu procesu (Web, WebJob)
Pro hlubší diagnostiku je někdy potřeba získat memory-dump procesu. Ukážeme si teď, že to jde i z Azure Web Apps (WebSites), včetně jejich WebJobs.
Kudu Process Explorer
Nejjednodušší situaci máme, pokud chceme získat aktuální memory-dump 64-bitového procesu. Stačí otevřít management-site Kudu (to je ten webík, který najdete na adrese https://yoursitename.scm.azurewebsites.net), v Process Exploreru pravým tlačítkem myši otevřít kontextové menu příslušného procesu (kromě w3wp.exe jsou tam i WebJoby) a z něj vybrat Download Memory Dump:

…memory-dump se začne po chvilce (někdy i po několika minutách!) stahovat.
Kudu Debugging Console + ProcMon ze SysInternals
Nejobvyklejší komplikací je, pokud nám na 64-bitovém stroji (App Service Plan) běží 32-bitový proces (což je pro Azure Web Apps poměrně obvyklé, chceme šetřit vzácnou pamětí). Výše popsaný postup přes Process Explorer nám vytvoří 64-bitový dump 32-bitového procesu, s kterým mnoho muziky nenaděláte, byť třeba Visual Studio si s ním celkem poradí (stejný problém, jako když použijete pro vytvoření dumpu 64-bitový TaskManager).
Naštěstí Kudu má kromě Process Exploreru i Debug Console – CMD i PowerShell. Navíc v cestě D:\devtools\sysinternals\ najdete na WebApps stroji předinstalované utility SysInternals vč. ProcDump (dříve bývalo potřeba ProcDump stáhnout, ale už ani to není potřeba).

Je zde opět jedna záludnost. ProcDump je potřeba spouštět s přepínačem -accepteula, protože jinak se při svém prvním spuštění dožaduje akceptace EULA v GUI (což nevidíte), a dokud licenční smlouvu neakceptujete, otravuje s tím stále (což se v konzoli projevuje tím, že ProcDump nic nedělá).
ProcDump lze samozřejmě použít kromě prostého získání aktuálního dumpu i v mnoha pokročilejších scénářích – obdobně jako DebugDiag mu lze říci podmínky, na jejich splnění má čekat a dump vytvořit. Viz ProcDump reference.
.NET Memory Internals & Advanced Debugging – záznam, dema a slides [WUG Brno 05/2016]
Slides a dema z mé přednášky pro WUG Brno z 31.5.2016:
- 2015-05-31 – WUG Brno – .NET Memory Internals & Advanced Debugging – slides [PDF]
- 2016-05-31 – WUG Brno – .NET Memory Internals & WinDbg – Dema [ZIP, 900 KB]
Záznam z přednášky je publikován na našem HAVIT YouTube Channel.
Dotčená témata
- StackOverflow troubleshooting
- Stack
- Windows Debugger, SOS, DebugDiag
- Heap
- Garbage Collector
- Prozkoumávání haldy, rootů, finalization queue
- GC Root identification (static fields)
- Debugging dema