Thursday, September 27, 2007

TortoiseSVN's "Noisy" TSVNCache.exe

A co-worker of mine pointed me to a post by Travis Illig on his blog titled "Optimize Tortoise SVN Cache (TSVNCache.exe) Disk I/O"

A short synopsis:



TortoiseSVN has a shell-overlay that indicates the status of your files and folders within an SVN 'working folder'.

The background process that 'watches' your file system for SVN-related files and folders is TSVNCache.exe.

TSVNCache.exe, according to Travis, was bogging his system down with disk I/O.

Instead of disabling TSVNCache.exe entirely (and thereby disabling the icon-overlays) he found a way to limit what TSVNCache.exe 'watches' on the file system.


When I first read his post, I thought "How bad can TSVNCache.exe be?" So, as noted in the comments of Travis' post, I grabbed FileMon from SysInternals, and fired it up. After setting a filter so it would only display TSVNCache.exe-related events, I was amazed at just how much 'noise' it was causing -- and I wasn't doing anything at all! Just sitting there watching FileMon do it's thing.

What really caught my eye was when I sent my coworker an instant message, thanking him for the link. My IM client wrote the message to it's log file, and I saw TSVNCache note the change, and check the log file's location for the existence of a ".svn" folder. (The existence of such a folder would indicate that the folder is an SVN working folder.)

I was surprised to say the least, and wanted to see how bad it really got. I 'putzed ' around doing a whole lot of nothing other than navigating around my file system. I seemed ridiculous at just how much disk I/O TSVNCache.exe was inducing.

For example, look at the following screen shot. It's the TSVNCache activity imposed when I copied a folder on my desktop, which contains one file:



Thankfully, by limiting which folders TSVNCache.exe 'watches' you can greatly limit this superfluous disk I/O. In case you still haven't taken a moment to click-through and read Travis' post, here's the quick run-down:


  1. Open your TortoiseSVN settings (Right-click the desktop, TortoiseSVN -> Settings)

  2. Go to the "Icon Overlays" item in the treeview.

  3. Add "C:\*" to the "Exclude Paths" box

  4. Add the folders you do want included in the "Include Paths" box
    • I entered my 'work' folder: "C:\Work\*", for example.

  5. Save changes : Apply/Ok

  6. Restart TSVNCache.exe - kill TSVNCache.exe in your Task Manager. It will automatically restart when you open a Windows Explorer.




With these changes in place, within FileMon, you'll notice that TSVNCache is a lot less noisy. It will still receive "Change Notify" messages, but you won't see it access files/folders outside of your specified "Include" list.


Thanks for the great tip, Travis!

Tuesday, September 18, 2007

Diff / Merge Tools


I've been using TortoiseMerge (part of TortoiseSVN) for a while now to view my source's diff's as well as handling merge conflicts. Something about it though has always felt 'clunky' to me. It may just be it's default color scheme that causes my mental detraction, but when I saw Larkware plug something call DiffMerge 3.1 beta I figured I should give it a try.



So far, I like it. It's UI is more sexy, as far as I'm concerned. It supports 3-way merging (like TortoiseMerge), making life so much easier when resolving merge conflicts. It supposedly supports folder diff/merging, although I haven't tried that yet.

I just wish I hadn't also accidentally stumbled upon another free diff/merge tool called KDiff3. I like KDiff3's shell context menu for it's ability to 'remember' a file's location on the file system. Once a file is 'remembered', you can perform a diff on any other file on the system by asking KDiff3 to compare the current file you have in front of you, and one of the 'remembered' files from earlier. (I've found myself needing to do this lately and KDiff3 definitely speeds up the process.)

One other thing I liked about KDiff3 was in it's installer: It provided an install option to integrate with TortoiseSVN as Tortoise's primary diff/merge tool.

The only problem with KDiff3 was, it's color scheme made me throw up in my mouth so fast that I quickly decided I had to give DiffMerge another shot. (The screenshot shown here doesn't truly show off the vomit-in-mouth colors so much...) KDiff's ability to integrate easily with TortoiseSVN got me looking into how to set DiffMerge as my Tortoise diff/merge tool.

Being perpetually low on free time, and not wanting to figure out the command line options myself, I enlisted Google to give me answers. In my search for integrating DiffMerge as my TortoiseSVN diff/merge tool, I ran into this post by 'Trumpi' detailing exactly how to integrate the two! So, instead of outright stealing from Trumpi, I give you: Trumpi's blog : SourceGear release DiffMerge.

...
Ok, I'll steal a little, only because I tweaked my settings slightly for the diff-tool specification (caught the tweak in Trumpi's post comments). Here's the command lines for specifying DiffMerge as your diff and merge tools for TortoiseSVN:
  • Diff:
    • New way: (see comments)
      C:\Program Files\SourceGear\DiffMerge\DiffMerge.exe /ro2 /t1=%bname /t2=%yname %base %mine
    • Old way:
      C:\Program Files\SourceGear\DiffMerge\DiffMerge.exe /t1=Base /t2=Mine %base %mine

  • Merge:
    • New Way: (see comments)
      C:\Program Files\SourceGear\DiffMerge\DiffMerge.exe /r=%merged /t1=%yname /t2=%bname /t3=%tname /c=%mname %mine %base %theirs
    • Old Way:
      C:\Program Files\SourceGear\DiffMerge\DiffMerge.exe /t1=Mine /t2=Base /t3=Theirs /r=%merged %mine %base %theirs



Update:

Note on the updated TortoiseSVN external tool commands: In the comments, Travis mentioned that I should RTFM. Upon inspecting the DiffMerge manual, I came up with the updated command lines above. I especially appreciate the "/ro2" option for the diff-tool. This makes DiffMerge act strictly like a diff-tool (ie: no editing allowed.)

Also, I gave Jimmy's WinMerge a try. It does fit my 'eye-candy' requirement, but I just can't devote more time to it because of it's lack of 3-way merging.

Wednesday, September 05, 2007

Google Earth update

Hey, did anyone else notice the cool additions they made in Google Earth?

Check Out The Details Here.

Of special note, you can now flip-around and view the sky from your current location on Google Earth! This is really cool. There is also a built-in flight simulator. It's pretty easy to crash in the flight sim, so, I didn't really play much with it, just the 'view sky' mode.

Anyway... Quick post!

Monday, August 27, 2007

Thursday, July 26, 2007

I Have Adam's Book!

Just wanted to say that I got Adam Nathan's COM Interp book, and I'm already taking notes as to what I need to blog about. :) It's already answering questions that I've had, and while I thought what I was recently reading was going to give insight into the problems I had previously, it just skirted the issue and said "More in Chapter 7, 20, and 24".

This book is huge. We're talking bigger-than-the-Bible huge. So big, they wouldn't bind it with just one binding. No, they bound it as two seperate tomes. Tomes of arcane COM knowledge. ;)

Although I'm only into chapter 4, I can tell this book has what I need. Heh, well, I've also heard that if this book doesn't have the answers to your COM-interop questions, then no other book will, soooo. :) It hasn't been updated for .NET 2.0/3.0/3.5. I'm curious to know if any of the version-specific remarks Adam makes regarding the implementation of the SDK at the time of his writing have changed. I would guess the answer is "not much" because who would miss the chance to write a second edition!? :D

Anyway, before I go, does anyone else think it's kinda weird the way I just happened to have "N" post-it notes for Adam *N*athan's book?  
Posted by Picasa

Wednesday, July 18, 2007

VMWare : Loosing eth0 after you've copied your VM

Background

Here's a bit of weird-behavior I've noticed when working with some of our production virtual machines (running Gentoo Linux) here at work.

In order to update the OS's on our virtual machines, I will copy them to my local machine, power them on, update them, and then push the updated OS's back out into production at the earliest convenience.

When you copy the VM from one location to another, VMWare notices this and asks you "Hey, it looks like this machine has been physically moved or copied, do you want me to create a new VM-UUID?" If you answer in the affirmative, VMWare internally regenerates any unique-identifiers tied to this virtual machine. The one thing that's really noticeable is that any virtual ethernet adapters get their MAC addresses changed.

The problem I've experienced is that when you power on the new-UUID'd VM, you no longer have an ethernet adapter. Gentoo tries to bring-up eth0 and it says "network interface eth0 does not exist" and "Please verify hardware or kernel module (driver)"


Explanation

"So, what's going on?"

Try a couple things:
  • If you run lspci you should still see the ethernet adapter.

  • If you run 'dmesg' and should see the kernel find the network card and it even calls it eth0


"So, where does eth0 go?"

Try running ifconfig -a. I bet you now have an eth1 and it's MAC address matches the newly-generated virtual MAC address specified in the virtual machine's .vmx file.

"Oh great, so every time I copy the VM I need to update the system configs to use the new eth1, or eth2, etc!?!?!"

No, hush, I'm getting to the answer.

The problem stems from the linux distro 'remembering' the MAC address of the network adapter and expecting it to be the same between boots. In the case of our Gentoo VM's, it's udev that mucks this up.

"Ok, fine, it's udev's fault. We know it's broken because it's expecting the ethernet adapter to have a MAC address that it no longer has. What to do?

The Answer

To fix this problem you need to tell your linux distro the VM's new MAC address. How you do this can vary by distro. In my spelunking, I found a few ways:

  • In Gentoo do one of the following: (Do #1, it's the easiest.)
    1. Delete /etc/udev/rules.d/70-persistent-net.rules and reboot. Your eth0 should be back.
      • 2007/09/13 Update: This almost-always works for me. But, for some reason, sometimes it seems to confuse udev even more; after rebooting, I'll have an eth2 or eth3. When this happens, I end up following #2, making sure the udev config file has 'eth0' listed, and not eth1, eth2, or eth3.
    2. Edit /etc/udev/rules.d/70-persistent-net.rules (or whatever it's named) to match your new MAC address and reboot. Your eth0 should be back.
  • Other distros:
    • Look for, (and edit if you find,) /etc/iftab
    • Look for, and delete, then reboot /etc/udev/rules.d/25-iftab.rules
    • Look for, (and edit if you find,) /etc/sysconfig/network-scripts/ifcfg-eth0



Give Credit Where Credit Is Due

I got hints from a number of pages, but in the end, it was the folks over at the VMWare discussion forums for the win:
VMWare Discussion Forums

Friday, July 13, 2007

Marshalling Arrays To VB6's COM Funland pt2

Ok, yesterday I figured out how to get an array of interfaces (actually, objects that implement an interface, but go along with my sloppy grammer, ok?) out of C#, through the COM Callable Wrapper (CCW) and into COM-land.

Wouldn't you know it, but I also need to be able to pass an array of objects back into C# from COM-land.

Well, I'm here to say that figuring this out was a lot easier than yesterday's problem. At first I tried something like:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects(IBusinessObject[] bos);
}



Well, that doesn't work. In VB6, when you try to compile something that calls TakeBusinessObjects(...) you get the a compile error like the following:


Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic



Well, my Google digging on that particular error actually proved fruitful and the answer is simple:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects(ref IBusinessObject[] bos);
}


(credit goes to Jon Wojtowicz for his Using COM Callable Wrappers to Extend Existing Visual Basic 6.0 Applications post at EggHeadCafe.)

Hooray! That works! The array gets passed back into .NET-land, and things seem happy. Except, I wouldn't be writing this post if I didn't have a problem, right? Well, TakeBusinessObjects(...) doesn't throw an exception, and it reaches it's return statement successfully. Unfortunately, something gets lost in translation while returning control to VB6-land because I intermediately upon returning, VB6 raises this error:


Class does not support Automation or does not support expected interface
Number: 430



This error makes it sound like I've developed on v2 of some COM component, but I've deployed the compiled EXE on a machine that only has v1 of the COM component. What I don't understand is that the array gets passed through the CCW into .NET-land! It works! Something just goes wrong on the way back to VB-land.

Update: The Answer! (Kind of)


I gotta hand it to my buddy Jimmy - that guy has given me the "Try XYZ" that has fixed whatever problem I was tackling so many times -- and he's come through once again! He suggested that, I take a look at the [In] and [Out] attributes.

By default, when I compile my COM component, it's being assumed that I not only want to be able to take in an array reference, but that I also want to push any changes made to that array reference back out to the caller. Well, lucky for me, I don't make any changes to the array once it's in .NET-land, and I can flag the parameter as only needing to come into the method, and not out. Like this:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   void TakeBusinessObjects([In] ref IBusinessObject[] bos);
}



So, while this fixes most of the situations where I would need to pass an array from COM-land into .NET-land, it still irks me that I don't know why VB6 throws that error if the CCW marshaller tries to move the array back out of .NET-land when the method returns.

Wednesday, July 11, 2007

Marshalling Arrays To VB6's COM Funland

Ok, so, please, don't ask why I've found myself writing a COM component in C#, and am using it in VB6. Just accept that fact that I need to.

This COM component needs to return an array of data to VB6. At first I thought "Why not just have my C# project reference the VB6 runtime via interop, and I'll be able to instantiate a VB6 Collection class, and return that to VB6, and it will be happy.

But no, when I try to instantiate a VBA.CollectionClass I get:


Retrieving the COM class factory for component with CLSID {A4C4671C-499F-101B-BB78-00AA00383CBB} failed due to the following error: 80040154.




After much googling, the closest answer I could get was that I was trying to instantiate a VBA.Collection on an x64 platform. Um, the last time I checked my Pentium-D was a 32 bit processor. I even went so far as to force my C# COM component to compile to x86 specifically instead of the 'Any CPU' configuration. Still, the error persisted.

So, I had to bail on that idea, and come up with "Hey, what about just returning an array? What a great idea! I performed a test.

I created a test COM visible interface and class, something like:



[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface ITest {
   String[] GetStrings();
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Test : ITest {
   public String[] GetStrings() {
      List foo = new List();
      foo.Add("hello");
      foo.Add("bye-bye");

      return foo.ToArray();
   }
}


Then, the corresponding test-code in VB6:


Dim testObject as Test
Dim strings() as String

set testObject = new Test
strings = testObject.GetStrings()



It worked.

I was happy, and I continued working on my C# code as planned.


Days later, I have hundreds of lines of code down in C#, and I'm at a good point to test what I've written. Now, my objects weren't going to be returning string-arrays like the test case above, rather, they're going to be returning arrays of an interface defined in my C# COM component, something like:


[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IBusinessObject {
   Int32 GetSomething();
}

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
   IBusinessObject[] GetBusinessObjects();
}



[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class BusinessObject : IBusinessObject {
   /* you get the idea */
}

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class Foo : IFoo {
   public IBusinessObject[] GetBusinessObjects() {
      List bos = new List();
      /* Add some BusinessObjects to 'bos' */

      return bos.ToArray();
   }
}



Then, the corresponding VB6 code:


Dim foo as Foo
Dim myObjects() as BusinessObject

set foo = new Foo
myObjects = foo.GetBusinessObjects()




Unfortunately, it didn't work. While no exception was thrown in C#, somewhere between return bos.ToArray() and VB6's assignment to the myObjects array a "Type Mismatch" error was thrown in VB6. I couldn't figure out why, though.

I tried catching the array returned from .GetBusinessObjects() into a Variant like:


Dim myObjects as Variant
myObjects = foo.GetBusinessObjects()



I still received the "Type Mismatch" error. I was really lost here, because in VB6-land a Variant is the closest thing you're going to get to a generic object pointer as you're going to get.

I again didn't garner much assistance from another thorough Google spelunking. In my searches, I did stumble across the MarshalAs attribute, but since I'm not a COM-master I wasn't entirely sure what I should be marshaling an array of interfaces as in order to safely reach COM-world. I blindly tried a number of things, and always ended up with "Type Mismatch". I was loosing hope. (As a side note I desperately need to get a copy of Adam Nathan's book .NET and COM: The Complete Interoperability Guide.)

Finally, I stumbled upon it, and I'm not entirely sure why I didn't try it first:

The Answer!

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
public interface IFoo {
   [MarshalAs(UnmanagedType.AsAny)]
   IBusinessObject[] GetBusinessObjects();
}



What's weird is that this produces a compiler warning making it sound like the attribute is not of any use:


Type library exporter warning processing 'MyNamespace.IFoo.GetBusinessObjects(#0), MyProject'. Warning: Type contains [MarshalAs(AsAny)], which is only valid for PInvoke. The MarshalAs directive was ignored.



If you place the attribute on the actual implementation you do not get the compiler warning...but you also get the Type Mismatch again. So, it would seem the warning is ignorable as it is applying to something.

Something tells me that once I get my hands on Adam Nathan's book, this will become a lot more obvious to me, and/or I'll find a better answer. Either way, I've got a solution for now.


So, why this post? Mostly as a note-to-self as to how to solve the problem in the future, but there's the hope that some poor sap such as myself has had the exact same problem and that this will turn up for them while they spelunk Google.

If this proves useful to anyone, please, drop me a line.

Thursday, June 28, 2007

Weird SQL2005 TempDB Table and Primary Key Behavior

Ok, here's something I ran into the other day while working with some temp tables. I needed a temp table. That table was probably going to be bloated, and could benefit from a primary key and some extra indexes on it since I'd be doing some heavy queries against the table. The SQL used in this post isn't the same SQL as from my app, rather, it's been simplified to the point of demonstrating my problem.

Consider the create-table statement below:


Figure 1


CREATE TABLE #myTmp (
   [id] [int] NOT NULL,
   [id2] [int] NOT NULL,
   [foo] [decimal](6,3) NOT NULL,
   CONSTRAINT [PK_myTmp] PRIMARY KEY CLUSTERED ([id] ASC, [id2] ASC))



Ok, so this gives us a very simple temp table that's scoped to the connection (or stored procedure) that it's create inside. It also has a primary key.

I'd been designing this temp table, and testing my SQL in a query window inside Management Studio. I had run this SQL, and the query window and it's connection were still open when I copy-n-pasted the code into my application and took it for a test run.

I got an error:
"There is already an object named 'PK_TmpWorking' in the database."

Whaaaaaat? It's as though the name of the primary key doesn't follow the same scoping rules as other objects created in TempDB! This seems really weird to me, and my google-spelunking didn't turn up many answers (am I loosing my touch?) The only way around this was to not name the primary key constraint, like so:


Figure 2


CREATE TABLE #myTmp (
   [id] [int] NOT NULL,
   [id2] [int] NOT NULL,
   [foo] [decimal](6,3) NOT NULL,
   PRIMARY KEY CLUSTERED ([id] ASC, [id2] ASC))



This leaves SQL Server to come up with a name for the primary key, and it appears that it names it some random jibber-jabber.

So, while I can't really logic-out an answer as to why I'm seeing this behavior, I do at least have a work-around. But get this, as if this primary-key thing isn't weird enough, it would seem this 'bug' only exists for primary keys, and not other named indexes. What I haven't shown you is that, in my query window, I also had a number of CREATE INDEX statements, similar to:


Figure 3


CREATE NONCLUSTERED INDEX [IX_MyTmp_Foo] ON #myTmp ([foo] ASC)



I had this executed in my query window. The temp table had it's randomly-named primary key, and it had this named index. When I ran the same code in my application, there was no complaints at all. I would have expected to run into the same "There is already an object named 'IX_MyTmp_Foo' in the database." error, but I didn't get one!


Does any of this make sense? Can anyone explain why it appears that primary keys on temp tables aren't scoped within the same scope as the temp table itself? Especially since regular indexes appear to be scoped as you would assume.

Signed:

Confused in Yooperland.

Thursday, June 21, 2007

Oh wowwwww...

Ok, in this day in age, it's difficult to make me so awe-struck that my jaw hits the floor. Well, I finally got around to checking out the Tech Preview of Microsoft Live Labs' Photosynth, and let me tell you, my jaw is still on the floor. Totally awesome technology.

I'm going to give you guys two links, one that will take you to a MS Live Labs blog post containing a video of a guy at TED (Technology, Entertainment, Design) Conference in Monterey, California explaining a couple of new MS-acquired technologies. Photosynth is included. Find that post here.

Then, go and install and try Photosynth yourself. You will need to install the browser plugin to use it.

Friday, February 16, 2007

Eye Strain

Ok, nothing like posting only to link someone else, but here goes!

I found 22 Ways to Reduce Eye Strain at Your Computer via LifeHacker.

Now, I've stared at computer screens for a fairly large portion of my life, and only in the last couple years have I started developing eye-strain related ailments (head aches, and occassional blurred vision.)

Other things I found via the above links is How To Exercise Your Eyes, and a program called WorkRave. WorkRave may just replace my home-spun 'egg timer' application that bugs me at defined intervals to "'stand up, relax your eyes, etc...".

Enjoy!

Thursday, December 21, 2006

Does Granholm Live In Michigan?

What's wrong with this picture?



This is a postcard a coworker received from our freshly re-elected governor.

I like the attempt to include us yoopers. Unfortunately, well, I dunno. I've lived here a while, and something just doesn't seem right. Let's consult maps of Michigan at Google Images. (Go look.)

What really thrills my coworker is the shear number of hands this photo must have gone through before getting printed and *NOBODY* noticed it. Show anyone here in the U.P. this picture and they catch what's wrong instantly.

Now, I wasn't impressed with either candidate in this last election, but, well, who the heck is representing us?

UPDATE: I got a better copy of the picture...




Technorati Tags: , , , , , ,

Friday, November 17, 2006

VS2005 Pro FxCop Integration

This is mostly for my own "Hey, don't forget this link" purposes, but it's also for any of you whom, like me, aren't cool enough to have VS2005 Team Edition:

Integrating FxCop into VS2005 Professional Edition

Pretty nifty!

Monday, September 25, 2006

T.E.D.D.Y.

James sent me a link to a video demonstration of something called "T.E.D.D.Y." -- a simple 3D modeling program.

The link James sent me linked back to another blog that had found TEDDY, and that blog had a link to the original work, which can be found by clicking here.

Check out the video of TEDDY in action:



Wednesday, August 30, 2006

CheezLog

Hey, and old buddie of mine just started blogging. He's going to be getting himself up to speed with C#, and the XNA framework.

Since I don't have time to play with XNA myself, I hope to be able to live vicariously through his experiences. :)

Anyway, check out Cheez's blog:
CheezLog

Thursday, August 17, 2006

Classical Music Remixes

I'm in no way a music aficionado, and I have very specific likes and dislikes. Sometimes I like certain songs by a certain band, but really dislike the majority of other music in the same genre. It's weird.

One type of music I like almost 100% across the board is classical. I'm not a huge fan of overly-chamber-ish classical, and I don't really go outta my way to listen to classical. It's just decent background music. (No vocals == good background noise as far as I'm concerned.)

Now, remixed classical? I really like that. Check out this YouTube video for an example:



I'd really like to hear more remixes by this guy. Does anybody have any leads?


UPDATE: I found this YouTube vid because of digg.com, click here to digg-it. In the comments, someone has a link to JerryC's website. (I thought it was JerryL.). Anyway:

JerryC (mostly-English)

Collection of all his vides.

Wednesday, July 19, 2006

Debugging Threads In VS2005? Woe To Thee!

Ok folks, this is mostly a post for my own personal use. Basically, so the next time I run into the problem, I'll remember that I wrote this post. :)

While the title is misleading -- debugging in VS2005, especially debugging when debugging multithreaded apps. I've just ran into a problem though.

See, I'd set a breakpoint inside some code that's executing on another thread. When the breakpoint hits, VS2005 just sits there for about 10 seconds. It's like it's dead. Then, after it comes-to, if I try to step-through, or step-into any code after the breakpoint weird things happen. Like, it doesn't step-forward into the code. And while the IDE indicates the app is still in a 'paused' state, it certainly doesn't have any instruction pointer, nor does the "Threads" debugger panel indicate that your extra thread even exists anymore! Hitting "F5" and letting it go about it's business doesn't help either -- the app is now in some sort of voodoo-ized state.

The only solution is to stop the app and restart it.

This still doesn't help the fact that I can't debug any code running on a non-main/UI thread!


What to do?


Off to the MSDN forums for me!

Here's a post that helped me: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=285644&SiteID=1
That post links to this post:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=147707&SiteID=1
And that post links to this blog:
http://blogs.msdn.com/greggm/archive/2005/11/18/494648.aspx

Basically, what's happening is the debugger is trying to peek at all the local variables in order to 'help you' debug. Unfortunately, access the GUI objects created on the main/UI thread from another thread is bad juju, and the debugger hangs when it hits a breakpoint while trying to probe values from objects owned by the UI thread. Why it screws everything else up, I don't know.

BUT, I've come up with a solution not listed in the above links -- disable/close/don't-look-at the "Locals" debugging panel. If that panel isn't visible, the debugger won't waste time poking-n-prodding all the objects found in the current scope, and you'll avoid the problem entirely. :)

Tuesday, June 27, 2006

SQL MythBusters – MSDE/SQL Express has a 5 concurrent user limit

Having worked with various versions of MS SQL Server for a number of years now, (6.5, 7.0, 2000, 2005,) and the verious MSDE and 'Express' editions of the product, I found this following link interesting:

Euan Garden's BLOG : SQL MythBusters – MSDE/SQL Express has a 5 concurrent user limit

That '5 Concurrent User' limit has always been difficult to explain to our users...mostly because what we were explaining was never clearly explained to us... But anyway, I won't ramble on forever.

Wednesday, April 05, 2006

Monday, April 03, 2006

System.Runtime.Serialization.SerializationInfo -- .KeyExists()?

The Problem:

There are a number of collection-like objects in the .NET Framework that allow you to 'key' the data you enter into them. Dictionary<TKey,TValue> comes to mind almost immediately. Dictionary<TKey,TValue> contains a Dictionary<TKey,TValue>.ContainsKey(T key) method that makes key-existance determination trivial.

But what about the SerializationInfo object?

When an object implements ISerializable the ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) method adds the object's data to the serialization stream in a key/value style:

   info.AddValue("MyObjectsValueKey", this._MyValue);

Then, during deserialization, the constructor that meets the ISerializable implied-constructor signature:

   MyObject(SerializationInfo info, StreamingContext context)

is called, and the object is supposed to bootstrap itself from the data contained in the SerializationInfo instance.

How? By retrieving values based on the previously used string-keys, of course.

So, why isn't there a SerializationInfo.KeyExists(string key) method?

I hear some of you saying "Well, it's the serialization of an object, you had better know what's was serialized in the first place!" True, very true. But, consider a versioning issue that might arise:


Example (version 1):

Assume you have a business object that implements ISerializable. For this example, let's call that business object MyObject. In your first version of MyObject, you have some code that looks like this (large portions of code left out to save space):

[Serializable]
public sealed class MyObject : ISerializable {
private List<OtherObject> _OtherObjects;
private Int32 _CurrentOtherObjectIndex;

public OtherObject CurrentOtherObject {
get { return this._OtherObjects[_CurrentOtherObjectIndex]; }
set { this._CurrentOtherObjectIndex = this._OtherObjects.IndexOf(value); }
}


private const string OTHEROBJECTS_KEY = "___otherobjetskey___";
private const string INDEX_KEY = "___indexkey___";
private MyObject(SerializationInfo info, StreamingContext context) {
this._OtherObjects = (List<OtherObject>)info.GetValue(OTHEROBJECTS_KEY, typeof(List<OtherObject>));
this._CurrentOtherObjectIndex = info.GetInt32(INDEX_KEY);
}

private void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(OTHEROBJECTS_KEY, this._OtherObjects);
info.AddValue(INDEX_KEY, this._CurrentOtherObjectIndex);
}
}

As you can see, MyObject has an internal List<T> of OtherObject's. It exposes a property that returns whatever the 'current' OtherObject is, based upon a private index into the _MyOtherObjects List<T>.

The serialization code is fairly straight forward.

Now, let's say, somewhere in the development of the version 2, we find a bug related to _CurrentOtherObjectIndex not being updated properly when instances OtherObject are being inserted into the _OtherObjects List<T> at various locations, thereby possibly invalidating the OtherObject pointed to by _CurrentOtherObjectIndex.

Instead of updating the various code locations that would need to update _CurrentOtherObjectIndex we decide it would be better to just replace _CurrentOtherObjectIndex with an OtherObject instance. ie:

[Serializable]
public sealed class MyObject : ISerializable {
private List<OtherObject> _OtherObjects;
private OtherObject _CurrentOtherObject;

public OtherObject CurrentOtherObject {
get { return this._CurrentOtherObject; }
set { this._CurrentOtherObject = value; }
}
}

Ahhhh, but making this change would break the compatiblity between version 1 and version 2 of MyObject. Oh, but wait, we're handling the serialization ourselves, we should be able to code around it.
[Let's ignore any possible versioning issues involved with strong-naming, let's just assume the version 2 code tries to deserialize a version 1 MyObject.]

So, ideally, we could change the ISerializable implementation to look like this:

private const string OTHEROBJECTS_KEY = "___otherobjetskey___";
private const string INDEX_KEY = "___indexkey___"; // we need the v1 key
private const string CURRENTOTHEROBJECT_KEY = "___currentotherobjectkey___";
private MyObject(SerializationInfo info, StreamingContext context) {
this._OtherObjects = (List<OtherObject>)info.GetValue(OTHEROBJECTS_KEY, typeof(List<OtherObject>));

if (info.ContainsKey(INDEX_KEY)) {
// we're deserializing a version-1 MyObject. Update it to version-2
Int32 tmpIndex = info.GetInt32(INDEX_KEY);
this._CurrentOtherObject = this._OtherObjects[tmpIndex];
} else {
// we're deserializing a version-2 MyObject. Just get the deserialize the CurrentOtherObject
this._CurrentOtherObject = (OtherObject)info.GetValue(CURRENTOTHEROBJECT_KEY, typeof(OtherObject));
}
}

private void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(OTHEROBJECTS_KEY, this._OtherObjects);
info.AddValue(CURRENTOTHEROBJECT_KEY, this._CurrentOtherObject);
}

Unfortunately, we can't do this because there is no SerializationInfo.ContainsKey(string key) method! What we have to do is this:

private MyObject(SerializationInfo info, StreamingContext context) {
this._OtherObjects = (List<OtherObject>)info.GetValue(OTHEROBJECTS_KEY, typeof(List<OtherObject>));

try {
this._CurrentOtherObject = (OtherObject)info.GetValue(CURRENTOTHEROBJECT_KEY, typeof(OtherObject));
} catch (SerializationException) {
// we're deserializing a version-1 MyObject. Update it to version-2
Int32 tmpIndex = info.GetInt32(INDEX_KEY);
this._CurrentOtherObject = this._OtherObjects[tmpIndex];
}
}

We have to rely on an exception as part of our 'normal' code path. This feels 'icky' to me. ("Icky" being a highly technical term that means many things at different times. In this case, 'icky' means "goes against my 'best-practices' sense". Exceptions should be exactly that: an unexpected error condition. In the course of a normal deserialization, we have decided that being able to deserialize v1 MyObject instances into v2 MyObject instances is a completely normal operation. We should not have to rely on an Exception to perform normal work. And if we have a situation where v2 MyObject code may be deserializing a large amount of v1 MyObjects, we can expect it to be much slower as well because the Exception handling system carries a heavy tax.


One Possible Solution

One solution is to have any object that implements ISerializable also serialize some for of version information. Whether this version information is culled from the assembly versioning info, or is a private field of the class is completely up to your implementation. In the following modification to MyObject, MyObject will store it's own versioning information.

So, again from the top, version 1 of MyObject:

[Serializable]
public sealed class MyObject : ISerializable {
private static Int32 Version = 1;

private List<OtherObject> _OtherObjects;
private Int32 _CurrentOtherObjectIndex;

public OtherObject CurrentOtherObject {
get { return this._OtherObjects[_CurrentOtherObjectIndex]; }
set { this._CurrentOtherObjectIndex = this._OtherObjects.IndexOf(value); }
}


private const string VERSION_KEY = "___version___";
private const string OTHEROBJECTS_KEY = "___otherobjetskey___";
private const string INDEX_KEY = "___indexkey___";
private MyObject(SerializationInfo info, StreamingContext context) {
this._OtherObjects = (List<OtherObject>)info.GetValue(OTHEROBJECTS_KEY, typeof(List<OtherObject>));

// version 1 doesn't care about the VERSION_KEY value because it is the first version
this._CurrentOtherObjectIndex = info.GetInt32(INDEX_KEY);
}

private void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(VERSION_KEY, MyObject.Version);
info.AddValue(OTHEROBJECTS_KEY, this._OtherObjects);
info.AddValue(INDEX_KEY, this._CurrentOtherObjectIndex);
}
}

And the updated version 2 of MyObject:

[Serializable]
public sealed class MyObject : ISerializable {
private static Int32 Version = 2;

private List<OtherObject> _OtherObjects;
private OtherObject _CurrentOtherObject;

public OtherObject CurrentOtherObject {
get { return this._CurrentOtherObject; }
set { this._CurrentOtherObject = value; }
}


private MyObject(SerializationInfo info, StreamingContext context) {
Int32 streamVersion = info.GetInt32(VERSION_KEY);

this._OtherObjects = (List<OtherObject>info.GetValue(OTHEROBJECTS_KEY, typeof(List<OtherObject>

if (streamVersion == 2) {
this._CurrentOtherObject = (OtherObject)info.GetValue(CURRENTOTHEROBJECT_KEY, typeof(OtherObject));
} else {
Int32 tmpIndex = info.GetInt32(INDEX_KEY);
this._CurrentOtherObject = this._OtherObjects[tmpIndex];
}
}

private void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(VERSION_KEY, MyObject.Version);
info.AddValue(OTHEROBJECTS_KEY, this._OtherObjects);
info.AddValue(CURRENTOTHEROBJECT_KEY, this._CurrentOtherObject);
}

Tada. Clean, deterministic deserialization of version 1 and 2 MyObject instances.


Wrap-Up

So, does anyone out there know why there isn't a
SerializationInfo.ContainsKey(string key)
method? As with many things that I've learned about the .NET Framework, what at first seems obtuse to me usually has a very good explanation behind it.

I gotta give thanks to Mr. DotNet who has helpd me through those mentally-obtuse times. He really knows his stuff. I was hoping to be able to use his SyntaxHighlighter (*nudge*-*nudge*) to make my code a bit more readable. Maybe in a future update!


Technorati Tags: , , , ,