Me vs. modifying all triggers in a SQL Server database

by acha11 17. August 2010 17:00

The title of this post is a tribute to the fantastic Conor vs. SQL blog.

I was working on a database with several triggers on each table. The triggers audited INSERTs, UPDATEs and DELETEs by inserting records to shadow tables in a separate logging database. The database was backed up from one environment and restored to another in which the name of separate logging database was different. I wrote the following script to automatically update all the triggers, replacing any occurrence of the old logging database name with the correct new name.

Heads up: you could really do some damage with this script. Backup and test a restore from that backup before using it!

DECLARE @replaceThis VARCHAR(8000)
DECLARE @withThis VARCHAR(8000)
SET @replaceThis = 'OldLogDatabaseName'
SET @withThis = 'NewLogDatabaseName'
DECLARE c Cursor
FOR
    SELECT
        object_id,
        SCHEMA_NAME(schema_id) AS schema_name,
        name AS object_name
    FROM sys.objects
    where type_desc = 'SQL_TRIGGER'
    ORDER BY modify_date;
OPEN c
DECLARE @triggerObjectId AS INT
DECLARE @triggerSchema AS NVARCHAR(4000)
DECLARE @triggerName AS NVARCHAR(4000)
DECLARE @triggerDefinition AS NVARCHAR(MAX)
DECLARE @sql AS NVARCHAR(MAX)
FETCH NEXT FROM c INTO @triggerObjectId, @triggerSchema, @triggerName
WHILE (@@FETCH_STATUS <> -1)
BEGIN
    SELECT    @triggerDefinition = m.definition
    FROM    sys.sql_modules m, sys.triggers t
    WHERE    m.object_id = t.object_id
        AND t.object_id = @triggerObjectId
    PRINT 'Dropping ' + @triggerName + '...'
    SET @sql = 'DROP TRIGGER ' + @triggerSchema + '.' + @triggerName
    EXEC sp_ExecuteSql @sql;
    PRINT 'Recreating ' + @triggerName + '...'
    SET @sql = REPLACE(@triggerDefinition, @replaceThis, @withThis)   
    EXEC sp_ExecuteSql @sql;
    FETCH NEXT FROM c INTO @triggerObjectId, @triggerSchema, @triggerName
END
CLOSE c
DEALLOCATE c

Tags: , ,

The thirty-six dramatic situations

by acha11 13. August 2010 17:55

Recently I went looking for the ultimate origin of the idea that there are only seven basic plots; that every story-teller since the Greeks has just been dressing one or more of the stories up in fancy new robes. They’re like platonic solids – try as you might, you’ll never find a new one.

One version has it that the seven basic plots are:

  1. [wo]man vs. nature
  2. [wo]man vs. [wo]man
  3. [wo]man vs. the environment
  4. [wo]man vs. machines/technology
  5. [wo]man vs. the supernatural
  6. [wo]man vs. self
  7. [wo]man vs. god/religion

But that reference is somebody’s memory of second grade. Incidentally, I’d like to know which second grade syllabus includes discussion of the plot form “woman vs. self”.

Unfortunately, I haven’t found a good reference. The Straight Dope has a nice overview of the meme; it lists variations which claim there are “n Basic Plots” for n = 1, 2, 3, 7, 20, 36 and 69.

The thirty-six dramatic situations is my favourite of those. Each situation is named and given a list of elements, for example situation 23, “Necessity of sacrificing loved ones”, calls for “a Hero; a Beloved Victim; the Necessity for the Sacrifice”.

I have attached a spreadsheet for you to download and complete. It’s more-or-less a short-form diary. Each day, you complete one row of the sheet, putting a tick under any of the thirty-six dramatic situations in which you were a participant that day.

ShortFormDiary

Short-form diary.xlsx (11.12 kb)

Tags:

Born like this or that – MF Doom feat. Bukowski

by acha11 10. August 2010 15:08

There is no wrong way to get into a musician. Still, I got into MF Doom the wrong way – via MC Paul Barman

I make def tunes, steal from MF Doom and Jeff Koons

But put that aside for now. MF Doom has a track “Cellz” which starts with an extended spoken word sample of Bukowski reading Dinosauria, We, an apocalyptic vision.

Good old fashioned investigative journalism reveals that the sample on Cellz is not the full text of the poem. Here’s a diff of the version in Cellz against the original. Lines (and a single word) omitted from the version on Cellz are in red.

Born like this
Into this
As the chalk faces smile
As Mrs. Death laughs
As the elevators break
As political landscapes dissolve
As the supermarket bag boy holds a college degree
As the oily fish spit out their oily prey
As the sun is masked
We are
Born like this
Into this
Into these carefully mad wars
Into the sight of broken factory windows of emptiness
Into bars where people no longer speak to each other
Into fist fights that end as shootings and knifings
Born into this
Into hospitals which are so expensive that it's cheaper to die
Into lawyers who charge so much it's cheaper to plead guilty
Into a country where the jails are full and the madhouses closed
Into a place where the masses elevate fools into rich heroes
Born into this
Walking and living through this
Dying because of this
Muted because of this
Castrated
Debauched
Disinherited
Because of this
Fooled by this
Used by this
Pissed on by this
Made crazy and sick by this
Made violent
Made inhuman
By this
The heart is blackened
The fingers reach for the throat
The gun
The knife
The bomb
The fingers reach toward an unresponsive god
The fingers reach for the bottle
The pill
The powder
We are born into this sorrowful deadliness
We are born into a government 60 years in debt
That soon will be unable to even pay the interest on that debt
And the banks will burn
Money will be useless
There will be open and unpunished murder in the streets
It will be guns and roving mobs
Land will be useless
Food will become a diminishing return
Nuclear power will be taken over by the many
Explosions will continually shake the earth
Radiated robot men will stalk each other
The rich and the chosen will watch from space platforms
Dante's Inferno will be made to look like a children's playground
The sun will not be seen and it will always be night
Trees will die
All vegetation will die
Radiated men will eat the flesh of radiated men
The sea will be poisoned
The lakes and rivers will vanish
Rain will be the new gold
The rotting bodies of men and animals will stink in the dark wind
The last few survivors will be overtaken by new and hideous diseases
And the space platforms will be destroyed by attrition
The petering out of supplies
The natural effect of general decay
And there will be the most beautiful silence never heard
Born out of that.
The sun still hidden there
Awaiting the next chapter.

When I started this post, it was going to be about the choices Doom or his producer had made in cutting down the full sample. But in hunting down references, I found this clip from the film Born Like This, which is the same Bukowski reading of the poem, and already omits the lines (and word) in red.

It seems as though Bukowski himself made the cuts; the clearer audio on the clip from Born Like This (without the ominous background loop) seems to show that it’s not a full reading of the poem that’s been cut down. So now I’m interested to know when and why he made the cuts, exactly. Anyone?

After this little nugget of sub-par digital curation, I’d like to make a few points:

  • “The rotting bodies of men and animals” in the Doom version is amazing. The beat brings out the sinuous-ness-ness of that line (and Bukowski’s reading of it).
  • “Still” got chopped, huh? A conscious decision? I prefer the line without.
  • “Radiated robot men”, huh? That’s so 1960’s it’s 1950’s.

Tags:

Frankston to Melbourne on foot

by acha11 5. August 2010 21:44

Since about 1998, I've been talking about walking from the Melbourne CBD to Frankston in a day. On Saturday, I finally got around to it, in the fine company of Pat and Mark.

As the crow flies, it's around 40km, but with a few detours to stick to the beach as far as possible, we ended up covering fairly close to 45km.


View Larger Map

   - Elapsed time was a shade over 11 hours including a luxurious lunch-time stop at Black Rock to chat with Chris, Lucy and the kids and then clear out two local shops: the local bakery of its pastry products, and the local menswear shop of its fresh woollen socks.
   - The route was pretty much as per the map below, except we took the beach-side promenade in a few spots, and between Chelsea and Seaford we walked on the beach a fair bit. Distance was probably bang on 45 km including all the little detours around the creepy jelly-fish on the beach. Mark kicked one and it TOTALLY moved, I swear.

Highlights:
   - Mark lost a glove, realised it, and then a kind lady walked past and told him she saw it about 200 metres back. Mark went back and got it.
   - Later, Mark lost both gloves.
   - Later still, Mark noticed Patrick's gloves were suspiciously similar to Mark's lost gloves. Drama ensued.

   - With about 13 kms to go, we were walking in darkness along a stretch of beach just before Seaford. The city lights looked amazing about 30kms behind us, and in front of us, the sodium-yellow lights of Frankston were doing that creepy night-time thing where you walk and walk and walk and walk, but they don't get any closer, even though they seem no more than a few hundred metres away. We'd checked google maps, and the beach was clear and passable all the way to Frankston. Which was strange, because a couple of hundred metres later, about 250 tons of heavy earth-moving and construction equipment loomed out of the darkness, surrounded by red and white "do not pass, this means you" tape completely blocking the beach. As we approached, a security guard came wandering down and said "sorry, you can't come through." By this point, we were going to be stuck with a seriously annoying detour to go back up to the beach, cross the highway, and then come all the way back. I went for the shocked empathy/sympathy angle and said "we're trying to walk to Frankston, what's the quickest way around?", but before I got to the end of the sentence, he'd already said, "awww, come on, then, go on through." Resolution: I want to see if this technique works more generally... next time a bouncer's giving me trouble at a pub, I'll just come out with "hey, go easy, pal, I'm just trying to get to Frankston", and see what happens. Not sure what I'll do if I'm in a pub in Frankston.

       On reflection, it was lucky that the guard was posted there. I'm fairly sure Pat would otherwise have hopped onto the Liebherr digger (which was about half as wide as the beach itself) and driven it direct, Rommel-style, down the beach to Frankston, then turned left and plowed directly through Bayside shopping centre to the station.

   - With about 10 kms to go, we'd hopped back over to the highway and Mark and Pat were feeling the distance in such a way that the pain was eased by alternatively jogging ahead quickly and walking backwards slowly. It was dark, and a semi-scary old-ish lady with a shopping cart was walking towards us. She passed Mark and Pat (who were jogging backwards at this point), stopped, and turned to watch them for a second. When I reached her, a few seconds later, we had a quick chat:
         Lady: "What, are you trying to get fit or something?"
         Me: "No, we've just walked from the city."
         Lady: "You've done WHAT?"
         Me: "We've walked down from Melbourne."
         Lady: "WHAT? That's what the TRAIN's for."
         Me: "We just found out about that, we're going to catch it on the way back."
         Lady: "Pffft.", walks away.
 
   - When we got to Frankston station at about 7:30pm to catch the 7:40pm train back to the city, we were met by a sign saying that trains were to be replaced by buses, starting at 8:10pm. We could deal with this - our train would still run. But then the good-cop bad-cop routine started - there was also a station staff lady asking everybody walking towards the platform "What are you doing here?! There are no trains! NO TRAINS!" Intrigued, and keen to get to the bottom of the issue in case we went to queue for the bus just to see a train pull out of the station five minutes later, I asked her "So, the sign's wrong then? It says that the buses don't replace the trains until 8:10pm. Should you update it?" And she said "No, the sign's right. It was for last night. Tonight's different. THERE ARE NO TRAINS!"
   
       I couldn't argue with that. The mistake was entirely mine for reading the sign on the wrong night. Cowed, we boarded the bus, and were carried home in silence.

Tags: ,

“Prodigy” doesn’t mean what I thought

by acha11 28. May 2010 00:53

Romans were particularly concerned by ‘prodigies’, odd things and events which seemed to be signs of the gods’ communication. A prodigy might be a deformed child at birth, a mole (reportedly) with teeth or an apparent shower of blood from heaven. Soothsayers and a priest stood by to list prodigies and interpret them.

- p. 299, The Classical World, Robin Lane Fox

Wiktionary has prodigy coming via Middle English prodige (“portent”), from Latin prodigium (“omen, portent, prophetic sign”).

Because of the phrase “child prodigy”, I grew up assuming that “prodigy” meant “gifted person”. Later, I think I must have seen it used in the sense of “prodigious feat”, which probably reinforced my belief.

It’s a neat little revelation. Normally, a gifted musician puts me in mind of the thousands of hours of work they’ve spent polishing whatever natural talent they began with. But the concept of “prodigy” jolts me into a world-view in which a musician’s talent can only be understood as an omen from the gods.

Also, the idea that a prodigy can be either a blessing or a curse for the messenger sets me reeling a bit.

“Messenger” is the wrong word, I think. If there’s a consciousness involved (a deformed child, say), then they’re more the slate on which the message is written than an active messenger; even a phenomenon (e.g. blood falling as rain) can serve as a prodigy.

I like the name “The Prodigy” a lot more now, even though this article says it’s taken from the Moog Prodigy synthesizer.

Tags:

SQL Server Reporting Services 2005 and 2008 – cancelling a long-running report causes the next “View report” to hang the UI until the original finishes

by acha11 26. January 2010 19:42

Sorry for the repost; this post made it live (and into my Google Reader history), and then mysteriously disappeared from my BlogEngine.NET installation on GoDaddy. I suspect somebody quietly restored my database from backup without 'fessing up. Anyway:

This issue’s really annoying. It’ll drive our users nuts. Defining the issue precisely was pretty interesting, though. I haven’t found the right set of google search terms to find others who’ve run into or analysed this problem yet; hopefully this will help someone else.

The background

SSRS is a reporting framework from Microsoft.

  • SSRS WinForms ReportViewer sits inside our WinForms client-side application.
  • It talks via Web Services to the SSRS middle tier hosted in IIS on our app servers.
  • SSRS middle tier stores report metadata and in-flight request information in databases in SQL Server (ReportServer and ReportServerTempDb)
  • SSRS middle tier generates reports off our reporting schema in a (separate) SQL Server database.

The scenario

You’re using SQL Server Reporting Services 2005 or 2008. You’re displaying reports by hosting the SSRS WinForms ReportViewer control inside your UI.

You launch a long-running report, and the “Report is being generated” message is displayed.

image

You realise you chose the wrong set of report parameters, so you press the red “Stop” button to cancel report generation:

image

Note that at this point, the long-running SQL Server call to get the report data is still running on the server-side.

You amend your report parameters and press “View report” again:

image

The eagle-eyed will notice that nothing’s changed. The user is given no feedback that the second "View report” command is being processed. Even worse, the UI thread is actually completely blocked, waiting for a call to the SSRS server to complete.

Investigation

How can this be happening? The ReportViewer seems to do the server call to get report data on a background thread, so that it can give us the nice hourglass from the first screenshot above, and yet the second call’s blocking the UI thread horribly.

If we re-run the test and pause the debugger while the UI thread’s blocked after the second “View report” command’s issued, we see the following threads:

image

And the main (UI) thread has the following call stack:

  • [Managed to Native Transition]   
  • System.dll!System.Net.Sockets.Socket.Receive(byte[] buffer = {byte[4096]}, int offset = 0, int size, System.Net.Sockets.SocketFlags socketFlags = None, out System.Net.Sockets.SocketError errorCode = Success) + 0xc4 bytes   
  • System.dll!System.Net.Sockets.Socket.Receive(byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socketFlags) + 0x20 bytes   
  • System.dll!System.Net.Sockets.NetworkStream.Read(byte[] buffer, int offset, int size) + 0x84 bytes   
  • System.dll!System.Net.PooledStream.Read(byte[] buffer, int offset, int size) + 0x1b bytes   
  • System.dll!System.Net.Connection.SyncRead(System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}, bool userRetrievedStream = true, bool probeRead = true) + 0x12a bytes   
  • System.dll!System.Net.Connection.PollAndRead(System.Net.HttpWebRequest request, bool userRetrievedStream) + 0x5a bytes   
  • System.dll!System.Net.ConnectStream.PollAndRead(bool userRetrievedStream) + 0x1b bytes   
  • System.dll!System.Net.HttpWebRequest.EndWriteHeaders(bool async) + 0xa2 bytes   
  • System.dll!System.Net.HttpWebRequest.WriteHeadersCallback(System.Net.WebExceptionStatus errorStatus, System.Net.ConnectStream stream = {System.Net.ConnectStream}, bool async) + 0x16 bytes   
  • System.dll!System.Net.ConnectStream.WriteHeaders(bool async) + 0x2d1 bytes   
  • System.dll!System.Net.HttpWebRequest.EndSubmitRequest() + 0x82 bytes   
  • System.dll!System.Net.HttpWebRequest.SetRequestSubmitDone(System.Net.ConnectStream submitStream) + 0xf7 bytes   
  • System.dll!System.Net.Connection.CompleteConnection(bool async, System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}) + 0x158 bytes   
  • System.dll!System.Net.Connection.CompleteStartConnection(bool async, System.Net.HttpWebRequest httpWebRequest) + 0x177 bytes   
  • System.dll!System.Net.Connection.CompleteStartRequest(bool onSubmitThread, System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}, System.Net.TriState needReConnect = True) + 0x9a bytes   
  • System.dll!System.Net.Connection.SubmitRequest(System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}) + 0x293 bytes   
  • System.dll!System.Net.ServicePoint.SubmitRequest(System.Net.HttpWebRequest request = {System.Net.HttpWebRequest}, string connName = "S>System.Net.NegotiateClient/25/S") + 0x7c bytes   
  • System.dll!System.Net.HttpWebRequest.SubmitRequest(System.Net.ServicePoint servicePoint) + 0xf9 bytes   
  • System.dll!System.Net.HttpWebRequest.GetResponse() + 0x270 bytes   
  • System.Web.Services.dll!System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(System.Net.WebRequest request) + 0xd5 bytes   
  • System.Web.Services.dll!System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(System.Net.WebRequest request) + 0x5 bytes   
  • Microsoft.ReportViewer.Common.dll!Microsoft.SqlServer.ReportingServices2005.Execution.RSExecutionConnection.GetWebResponse(System.Net.WebRequest request) + 0xe bytes   
  • Microsoft.ReportViewer.WinForms.dll!Microsoft.Reporting.WinForms.ServerReportSoapProxy.GetWebResponse(System.Net.WebRequest request) + 0x10 bytes   
  • System.Web.Services.dll!System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(string methodName, object[] parameters) + 0xad bytes   
  • Microsoft.ReportViewer.Common.dll!Microsoft.SqlServer.ReportingServices2005.Execution.ReportExecutionService.SetExecutionParameters(Microsoft.SqlServer.ReportingServices2005.Execution.ParameterValue[] Parameters, string ParameterLanguage) + 0x42 bytes   
  • Microsoft.ReportViewer.Common.dll!Microsoft.SqlServer.ReportingServices2005.Execution.RSExecutionConnection.SetExecutionParameters(Microsoft.SqlServer.ReportingServices2005.Execution.ParameterValue[] Parameters, string ParameterLanguage) + 0x42 bytes   
  • Microsoft.ReportViewer.WinForms.dll!Microsoft.Reporting.WinForms.ServerReport.SetParameters(System.Collections.Generic.IEnumerable<Microsoft.Reporting.WinForms.ReportParameter> parameters) + 0x2a1 bytes   
  • Microsoft.ReportViewer.WinForms.dll!Microsoft.Reporting.WinForms.RSParams.SaveControlValuesToReport() + 0x309 bytes   
  • Microsoft.ReportViewer.WinForms.dll!Microsoft.Reporting.WinForms.RSParams.viewReport_Click(object sender, System.EventArgs e) + 0x57 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Control.OnClick(System.EventArgs e) + 0x70 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Button.OnClick(System.EventArgs e) + 0x4a bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Button.OnMouseUp(System.Windows.Forms.MouseEventArgs mevent = {X = 44 Y = 3 Button = Left}) + 0xac bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Control.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button, int clicks) + 0x28f bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x885 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.ButtonBase.WndProc(ref System.Windows.Forms.Message m) + 0x127 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Button.WndProc(ref System.Windows.Forms.Message m) + 0x20 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x10 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg = 514, System.IntPtr wparam, System.IntPtr lparam) + 0x5a bytes   
  • [Native to Managed Transition]   
  • [Managed to Native Transition]   
  • System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(int dwComponentID, int reason = -1, int pvLoopData = 0) + 0x24e bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int reason = -1, System.Windows.Forms.ApplicationContext context = {System.Windows.Forms.ApplicationContext}) + 0x177 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int reason, System.Windows.Forms.ApplicationContext context) + 0x61 bytes   
  • System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form mainForm) + 0x31 bytes   
  • >    WindowsFormsApplication4.exe!WindowsFormsApplication4.Program.Main() Line 20 + 0x1d bytes    C#
  • [Native to Managed Transition]   
  • [Managed to Native Transition]   
  • mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) + 0x3a bytes   
  • Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2b bytes   
  • mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x66 bytes   
  • mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x6f bytes   
  • mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   

From the bolded lines, reading from the bottom up, we can see:

  • this thread’s pumping messages (RunMessageLoop), so it’s likely to be a UI thread;
  • it’s handling a button click on the ViewReport button (OnClick and viewReport_Click), so it is likely to be the blocked UI thread that’s causing us grief;
  • it’s making a synchronous Web Services call to the Report Server (via a proxy) to a method called “SetExecutionParameters”

So in reality, SSRS is actually making at least two Web Services calls to the Report Server. The first is a UI-blocking synchronous call to SetExecutionParameters, and the second is an asynchronous call to do the actual report generation.

Because the first click on “View Report” didn’t block the UI and in fact jumped straight to the friendly hourglass UI mode, we can infer that SetExecutionParameters either wasn’t called first time through, or it was called and was so fast that we didn’t notice the blocking.

While the UI’s blocked, let’s go the SQL Server and get a list of locks that processes are waiting on in either of our SSRS databases:

select *
from sys.dm_tran_locks  l
where resource_type = 'KEY' and request_status <> 'GRANT'
order by resource_database_id, request_owner_id, request_request_id

image

So, somebody’s waiting for a LOCK on a KEY resource (roughly, a particular value in an index) with resource_associated_entity_id = 72057594038386688. What does that number correspond to?

image

Okay, somebody’s waiting on a lock on a KEY value in the SessionLock table in ReportServerTempDb. Sure looks as if there’s a session-level lock, and the first report generation server call is holding that lock while the second “SetExecutionParameters” request waits for it to complete. Because (and this is the whole point of supporting cancellation) that first generation call is taking a very long time to complete, the user’s going to be stuck waiting for a very long time, with the UI completely hung, before the second request is cancellable and actually commences the real work of generating report results.

This sucks.

Analysis

While it looks like the SSRS WinForms ReportViewer control has a nice asynchronous design that avoids blocking the UI thread, it still blocks in exactly the situation where it’s most irritating.

  • SSRS has a SessionLock which serializes certain operations at a per-Session level.
  • SetExecutionParameters and report generation both require the SessionLock, so can’t run concurrently for a given session.
  • The SSRS team didn’t find and implement a reliable way to cancel an in-flight request before processing a second request.
  • So if a long-running report generation process kicks off, any subsequent requests from that session will have to wait until the first request dies or completes.
  • SSRS backgrounds the report generation call. This is good; users get feedback that something’s happening, and the UI thread’s not blocked, so the rest of the application is still usable.
  • SSRS mistakenly assumes that “SetExecutionParameters” will never be long-running, so doesn’t background it. This assumption is wrong, because it can be blocked by an earlier report generation call that’s still running. When this happens, the UI thread’s blocked, and the client’s application hangs completely. User experience sucks.

Workarounds

  1. Do nothing.
  2. Write your own ReportViewer alternative that backgrounds all potentially long-running server calls. A lot of work, I know.
  3. Hide the Stop button (set the ShowStopButton property to false on the ReportViewer instance) and provide your own Stop button outside of the control. This external stop button then resets or even re-instantiates the control completely, causing a new user session to be created, so there’s no contention on SessionLock, so the user experience is improved. ReportViewer property values that were previously set in the designer will have to be re-applied. Horrible, I know.
  4. Hook the ReportViewer’s RenderingComplete event. Check RenderingCompleteEventArgs for an exception whose message contains “the operation was canceled”. If that exception’s present, then assume the user clicked “Stop”. Re-create or Reset() the Report viewer. Horrible, I know.

We’re likely to go with option 4 at this point.

Tags:

I stumbled across some brilliant writing this morning

by acha11 13. January 2010 13:41

It's a tutorial page for a strategy game set in hell. I haven't played the game, but the tutorial is good enough that i might have to try it. Or get Pat to try it and let me know if it’s any good.

The premise is that your "tutor" is a prince of hell, and he's an articulate but hate-filled thing-that-should-not-be who's completely underwhelmed by your mental prowess.

So he says wonderful, horrible things like:

   "and yet... i need... your help. When i said "need", i chewed off a small piece of my lip. When i said "help", i vomited. Consuming it calmed me."

and

   "my tears of mirth track through my nasal vents to form a foamy broth that i will enjoy while you return to the map."

and

   "we will attempt to provoke moloch. more than once, he has refused my broths."

and (after explaining what "clockwise" means)

   "my appendages curl and uncurl as i picture your vacant stare".

My personal favourite is “More than once, he has refused my broths.” I’m just waiting for the opportunity to slip it in to conversation next time we’re discussing someone who’s made a habit of irritating me :-)

 

Tags:

Mandelbrot scaffolding

by acha11 2. January 2010 05:06

I’ve added a new feature to my Mandelbrot renderer.

Quick refresher: points are in the Mandelbrot set if, no matter how many times you apply a simple transform, they never escape to infinity.

The new feature shows the path taken while iterating a specific point.

Robot lollipop

This screenshot shows an ever-decreasing spiral. The start of the spiral is pretty close to the centre of the image. That’s point (0, 0) in the plane. The path jumps from there to the actual point being rendered, which I’m guessing is about (0.5, 0.5). From there, as we keep iterating, the point describes a shrinking spiral, never escaping to infinity.

Mandelbrot_Path_Plot_1

Spirograph

The path taken for this point (a bit up and left of the previous one) is a bit more interesting; it’s still describing a shrinking spiral, but the rate of shrink is a lot lower, and the angle is a lot tighter.

Mandelbrot_Path_Plot_2

Corporate logo #3414

And this path’s more interesting again – it’s noticeably asymmetrical.

Mandelbrot_Path_Plot_3

Qix 2010

This is the path taken by a point in the left-hand circle – these tend to be a lot less beautiful looking as screenshots, but seen live, the trajectories (which shift and reconfigure as you drag the mouse around) sweep between different “stable”, “aligned” configurations in a really interesting way. I need to hook up some video capture again.

Mandelbrot_Path_Plot_5

Here’s an example of a stable configuration that cycles around four nodes (even a long way outside the set proper) without ever actually escaping. Note that this isn’t a precise cycle – each iteration seems to be slightly different to the last.

Mandelbrot_Path_Plot_6

Escape

None of the five points so far have escaped to infinity, so their corresponding pixel is rendered white, and the path in the above shots is shown in grey. This screenshot shows what happens when a point does escape. The path looks a lot more unordered (although there is some structure), and it ultimately disappears off the top of the screen (well North of (0, 2)).

 

Mandelbrot_Path_Plot_4

Tags:

We built a town called “Wisdom”…

by acha11 15. December 2009 15:36

on a SUPERVOLCANO.

This is entry #4314 in the series “first lines of choruses of songs Andrew makes up in his head on the train.” This is a shouty chorus.

Thanks for your kind attention.

Tags:

Raymond Chen sums up software development; we can all go home

by acha11 14. November 2009 16:40

A beautiful thing to say

A few people have told me I find odd things beautiful. Well, consistent with that character trait, here’s a colossal generalization about software development that I find beautiful.

“Programming is about establishing invariants, perturbing them, and then re-establishing them. It is a game of stepping-stone from one island of consistency to another.”The Old New Thing

First off, this is a beautiful way of expressing what’s going on code. I like the echo of musical harmony, where you start at rest, then build tension, then release. I like the stepping-stone metaphor; you’re “in the air” and “unsupported” while you’re jumping from one consistent state to another. I like that it hints towards the difference between the code humans are capable of writing (and reading), and imaginable but currently not-feasible automatically generated systems, in which the invariants (stepping stones) are many and tiny, and of complicated shape; I have this intuition that there’s no reason other than maintainability-by-humans to hold design ideals of “define few invariants” and “define invariants that can be simply expressed”.

The more I think about it, though, the first sentence doesn’t match reality. Is “perturbing” an invariant similar to “violating” it? I guess not, since an invariant is a truth that holds universally over your model.

Letting go of your ideals

Imagine you have a model consisting of two variables, a and b, and an invariant that says “a + b = 0 at all times”.

If a starts out as –1, and b starts as +1; our invariant is satisfied. Assuming we can’t atomically modify both a and b in a single operation, we can’t change either a or b without violating the invariant. Even if the target state for the overall model satisfies the invariant (say a = –2, b = +2), there’s no sequence of operations we can apply without breaking the invariant on the way.

So we’re stuck; we have to weaken the invariant. Instead of preserving “a + b = 0”, we just preserve “a + b = 0 so long as the reader is the sole holder of a lock that governs read and write access”, say.

With this new, weaker invariant, we can take the read/write lock over a and b and make modifications, and then release the lock with a + b = 0, without ever having violated the invariant.

The new weak invariant hasn’t been violated. Has it been “perturbed”? No. At least, not on any interpretation I can come up with.

So where are we? We have the “ideal” invariant (which Raymond’s talking about):

  • a + b = 0

And the “pragmatic” invariant (weakened so that we can actually mutate our model):

  • a + b = 0 if you hold the lock.

Under this interpretation, of course it’s okay to violate (“perturb”, in Raymond’s terminology) the ideal invariant, since it’s just a convenient simplification to sit in a coder’s mental model. The real invariant is the weakened, pragmatic invariant, and it, by definition, is never violated.

Some coders work as if the ideal invariants actually hold

In the real world, probably the majority of coders I run into are of what I’d call the pragmatic-productive style. This style of coder tries approaches serially until something seems to work, and then, bang, moves on to the next business problem that needs solving.

Many (certainly not all) coders who work this way don’t “waste time” thinking about “pragmatic” invariants (e.g. “I need the lock if I want a consistent read of a and b”).

As a result, they write code that works under a single-threaded regime breaks horribly in multi-threaded land. I’m not talking about the incredibly difficult-to-eliminate “thread-safe code is almost impossible for humans to write” threading bugs, I’m talking about the trivial “this was never going to work from the moment two requests arrived at the web server within a second of each other” threading bugs.

There’s a lot of education/evangelism floating around geared towards educating this style of coder to embrace concurrency in a way that will produce code that works, as far as possible.

Some coders work as if invariants don’t matter at all

My bigger concern is that many coders don’t really think in terms of invariants much at all.

They’ll know that each row in your OrderLine table has to belong to a row in the Order table, sure.

But they won’t be able to explain what consistency rules they’re applying to the OrderLineHistory temporal table (with effective start date and effective end date) which they just created and populated. These consistency rules are invariants, and no-one can consume (read) the model without making assumptions about those invariants. It’s the responsibility of the designer to determine those invariants, and set about satisfying them in the code that’s written, communicating them to the people who depend upon the model, and enforcing them wherever feasible.

But, as I say, pragmatic-productive coders tend not to be very interested in this stuff.

As a tangent, invariants are great assertion-fodder, and it can be nice when writing unit tests to also implement a quick re-check of any assertions that have no framework-provided safety-net. This consistency check can be run after each test to verify that in addition to any directly expected behaviour your test was written to assert, the code under test doesn’t result in an inconsistent model.

They may or may not be smited. Smoten? Smitten?

Anyway, the result of ignoring invariants is systems that are difficult to describe, reason about, implement, test, and maintain. If you can’t tell me what invariants hold over the model you just designed, then you haven’t actually designed a model yet. And code that’s built to manipulate a model whose invariants haven’t been thought through will be a super-happy-fun-time fountain of bugs, keeping everybody entertained all the way up to a delayed acceptance test.

And that’s why pragmatic-productive coders, who are all about the time-to-feature and delivering-business-value (neither of which is bad in itself) should care about invariants: because ignoring them increases time-to-feature and delays delivery of business value.

Tags:

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

RecentComments

Comment RSS