Showing posts with label utility. Show all posts
Showing posts with label utility. Show all posts

Friday, June 26, 2009

Functional Optimistic Concurrency in C#

A few months ago Phil Haack wrote about how C# 3.0 is a gateway drug to functional programming. (Yeah, that's how long ago I started writing this blog.) I couldn't agree more. I find myself solving problems using functional rather than imperative programming quite often nowadays. It's much more elegant for many problem spaces.

Before we go any further, here's the sample app used for this article. Even if you don't like my writing, you should play with it. Yeah, you! optimistic-concurrency.zip

One problem space that fits very well with functional patterns is in developing apps that have to use optimistic concurrency to maintain data consistency at scale. Here at Hive7 we build PvP games. In such games, multiple people and background processes are often affecting the same entity at the same time. We can't use coarse grained locks or high isolation levels in MS-SQL, or the whole game would come to a halt. Here's a common scenario in a game like Knighthood:

Multiple rival lords are attacking my Kingdom at once trying to steal my most prized vassal, my wife! My wall is staffed with a heavy defense, and my hospital has a strong set of medics healing my kingdom over time. But to keep a handle on the attack I also have to continuously spend gold to heal my defensive army.

In this common use case there are a number of subtleties. First, multiple people are attacking me at once. That means they're doing damage to my defenses in real time, and at the same time. My hospital is healing my vassals over time. This occurs in a background process once every few minutes. And I'm triggering an instant heal to my defensive vassals using my gold supply. My Marketplace is also generating gold for me over time in another background process. To top it all off, this is happening across a cluster of application servers that are certain to be processing multiple requests simultaneously. Phew!

So what does all that mean? Well, basically, there are a lot of possibilities for change conflicts. And we have to deal with those conflicts to both keep a consistent data model and perform well.

There are a a number of potential strategies for managing these change conflicts in the persistent store – a few beefy Microsoft SQL Server databases in our case. We chose to go with optimistic concurrency and an abort on conflict transaction strategy. That basically means when we write data to the database we make sure we are always writing the most recent version of a row. If an application attempts to write an old version of the row, the data access layer throws an exception and aborts the transaction. Knighthood uses NHibernate so the validation is done for us automatically using a simple version number on the row. The basic algorithm is:

  1. Read data and serialize into objects (done by NHibernate)
  2. Modify objects in code
  3. Tell NHibernate to persist the changes, which does the following

    1. Increments the version number
    2. Finds all the changes and batches up insert/update calls
    3. Uses the version number in the WHERE clause of updates like: "UPDATE Table SET Col1='blah' WHERE Version=36"
    4. Checks the rows modified reported by SQL server and throws an exception if it's an unexpected number

As you can imagine, this fails regularly in a high concurrency scenario, but it succeeds orders of magnitude more often than not. It's also pretty standard for any web app nowadays.

The only problem is, to preserve consistency, an exception is thrown and the transaction is aborted when change conflicts occur. That means whatever request the application or user issued fails. We could show the user a friendly error message, but that would be a frustrating experience. Nobody likes seeing errors for non-obvious reasons. And in the case of headless software running in the background the error would just be in a log somewhere. If it's something important that needs to happen, then we have to make sure it gets done! So us imperative programmers devise a retry scheme and write a loop with an exception trap around our code. Maybe you get clever and create a class that does this which raises an event any time you need to execute your retry-able code. But, this gets pretty cumbersome. Enter functional programming!

We have a little class named DataActions that is used to simplify and consolidate this retry process and make it painless to use. I'm going to use LINQ to SQL as the example here. Here's some usage code:

DataActions.ExecuteOptimisticSubmitChanges<GameDataContext>(
dc =>
{
var playerToMod = dc.Players.Where(p => p.ID == playerId).Single();
SetRandomGold(playerToMod);
});

As you can see it's really straight forward. Notice all the goodness going on there. We don't have to instantiate our own DataContext, manually submit the changes, or worry at all about transactions. It's all handled by the wrapper. And, you just have to provide some code to execute once the DataContext has been instantiated.

The ExecuteOptimisticSubmitChanges helper method itself is pretty simple as well:

public static void
ExecuteOptimisticSubmitChanges<TDataContext>(Action<TDataContext> action)
where TDataContext : DataContext, new()
{
Retry(() =>

{
using (var ts = new TransactionScope())
{
using (var dc = new TDataContext())
{
action(dc);
dc.SubmitChanges();
ts.Complete();
}
}
});
}

And, finally, we have the Retry method:

public static void Retry(Action a)
{
const int retries = 5;
for (int i = 0; i < retries; i )
{
try { a(); break;
}
catch { if (i == retries - 1) throw;

//exponential/random retry back-off. var rand = new Random(Guid.NewGuid().GetHashCode());
int nextTry = rand.Next(
(int)Math.Pow(i, 2), (int)Math.Pow(i + 1, 2) + 1);

Thread.Sleep(nextTry);
}
}
}

When you string all this together you get pseudo-stacks that look like:

MyCode
ExecuteOptimisticSubmitChanges
Retry
ExecuteOptimisticSubmitChanges
MyCode

So, why should you care? The calling code is really easy to read, and you get a number of other benefits with this code. In addition to handling exceptions caused by concurrency errors, you also get retries on deadlocks, and more common Sql Connection errors.

I put together a little sample application you can play with. It uses these helpers and has a SQL Database with it. The sample simulates really high concurrency and you can watch it deal gracefully with deadlocks. Then you can change line 29 of Program.cs and execute the same concurrent code without retries enabled. It ouputs the number of failed transactions and a bunch of other interesting stuff to the console. Here's some example output:

 ...  Retrying after iteration 0 in 1ms Retrying after iteration 0 in 0ms Thread finished with 0 failures. Concurrency at 3
Retrying after iteration 1 in 3ms
Retrying after iteration 1 in 4ms
Thread finished with 0 failures. Concurrency at 2
Retrying after iteration 2 in 5ms
Thread finished with 0 failures. Concurrency at 1
Retrying after iteration 3 in 15ms
Thread finished with 0 failures. Concurrency at 0

0 total failures and 7 total retries.
All done. Hit enter to exit.

And the same test run with retries disabled:

 ...  Starting worker. Concurrency at 8
Thread finished with 0 failures. Concurrency at 7
Thread finished with 0 failures. Concurrency at 6
Thread finished with 1 failures. Concurrency at 5
Thread finished with 1 failures. Concurrency at 4
Thread finished with 1 failures. Concurrency at 2
Thread finished with 2 failures. Concurrency at 3
Thread finished with 0 failures. Concurrency at 1
Thread finished with 2 failures. Concurrency at 0

7 total failures and 0 total retries.
All done. Hit enter to exit.

Here's the download link again: optimistic-concurrency.zip

Let me know if you have any questions.

Tuesday, November 27, 2007

How much space are all my sql tables using?

I deal with a few dozen databases on a daily basis. Often times I wonder "What the heck is making this database 100GB?" Sure, you can click around in Management Studio and find figures on a table by table and index by index basis, but there has to be a better way!

So, I asked a colleague of mine – who happens to be the guy that did all the database work on the SoapBox products and the best database dude I know. He didn't have a good answer for me so he whipped up this tiny script [Edit: After reading my blog he told me he actually adapted the script from one he found on the net that didn't quite work] in a few minutes. It makes my head spin. It's not perfect, but it's darn close.

WITH table_space_usage
( schema_name, table_name, index_name, used, reserved, ind_rows, tbl_rows )
AS (
SELECT s.Name
, o.Name
, coalesce(i.Name, 'HEAP')
, p.used_page_count * 8
, p.reserved_page_count * 8
, p.row_count
, case when i.index_id in ( 0, 1 ) then p.row_count else 0 end
FROM sys.dm_db_partition_stats p
INNER JOIN sys.objects as o
ON o.object_id = p.object_id
INNER JOIN sys.schemas as s
ON s.schema_id = o.schema_id
LEFT OUTER JOIN sys.indexes as i
on i.object_id = p.object_id and i.index_id = p.index_id
WHERE o.type_desc = 'USER_TABLE'
and o.is_ms_shipped = 0
)
SELECT t.schema_name
, t.table_name
, t.index_name
, sum(t.used) as used_in_kb
, sum(t.reserved) as reserved_in_kb
, case grouping(t.index_name)
when 0 then sum(t.ind_rows)
else sum(t.tbl_rows) end as rows
FROM table_space_usage as t
GROUP BY
t.schema_name
, t.table_name
, t.index_name
WITH ROLLUP
ORDER BY
grouping(t.schema_name)
, t.schema_name
, grouping(t.table_name)
, t.table_name
, grouping(t.index_name)
, t.index_name

I hope that hurts your head as much as it does mine. Just goes to show that I'm no SQL guru, I guess. But hey, if you ever want to know where that space is going in your database, this script will tell you!

Monday, November 26, 2007

FileVersionInfo - My Long Lost Friend

I've been doing .NET development professionally since the first public beta release of the 1.0 framework. That's... well, a long time. It constantly amazes me how big the framework really is. There are a number of times I've written tens or hundreds of lines of code for a task that I later realized – or was smacked over the head with by a colleague with – that were in the framework. Heck, even after writing my Extension Methods post a couple months ago some friendly readers pointed out how I was writing too much code.

Today, I was updating a client's web application to show the version number at the bottom of the screen based on the actual version that was running (it used to just be hard coded). The version number on the site was supposed to show the full revision of the assembly – which is stored in the assembly file version. The process to build the web site follows Microsoft version standards so the assembly version number does not necessarily change with each revision, but the file version does. Therefore, the normal System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() isn't really the version number we want to display.

But how do you get the file version? Well, with System.Diagnostics.FileVersionInfo.GetVersionInfo of course! I swear I have looked for how to do this numerous times with only win32 responses to my query. Anyway, apparently it's dirt simple. This time I ran into this thread with the answer. Check out the System.Diagnostics.FileVersionInfo class for more info. Duh!

I leave you with this to ponder: WHY, WHY, WHY is this feature not exposed through System.IO.FileInfo or some other class that makes sense (and I did check in Reflector to be sure I wasn't mad)?!

Wednesday, November 21, 2007

Command Line VS 2008 Project Upgrade Tool

I've converted a few projects to 2008 RTM now (from 2003 to 2005 to 2008 beta projects), and I wish John Robbins had published this blog earlier. For those of us that don't like clicking in things he presents a built-in command line switch on devenv to upgrade solutions. I decided to take it to the next level.

I built a small command line application that recursively scans a directory for solution files and prompts you to upgrade all of them. I tried it on a bunch of my solutions and it seemed to work pretty well.

To use the tool you just run it from the command line and specify the directory where you want to start searching. Or you can specify no arguments to search the current working directory.

    upgrader.exe c:\root\of\your\tree

or

    upgrader.exe

That's it! Quick and easy. The output will look something like:

    Searching for solutions under c:\svn\websites
Upgrade "c:\svn\websites\mysite\my.sln"? [Y/N] n
Skipped "c:\svn\websites\mysite\my.sln"
Upgrade "c:\svn\websites\broken\site.sln"? [Y/N] y
Converting "c:\svn\websites\broken\site.sln"
Unable to convert solution "c:\svn\websites\broken\site.sln"
Check out the errors in the UpgradeLog.xml file.
Upgrade "c:\svn\websites\ok\site.sln"? [Y/N] y
Converting "c:\svn\websites\ok\site.sln"
Converted "c:\svn\websites\ok\site.sln"

Well, that's my simple app that took just a few minutes to write and saved the pain of many clicks through a wizard. Download it here.

Thursday, September 27, 2007

Stringbuildify!

A task that I often end up doing when coding an actual web site (i.e. not writing a sample or some such) is adding client script to a page/control in codebehind using the ClientScriptManager. Let's say you've got the following alert script you want to add to the page so you can use it in a control:

function doAlert()
{
alert('welcome!');
}
Well, there are now a number of ways to get this into your page, but the quickest, in-line way is to use the ClientScriptManager. Like so:
if (!Page.ClientScript.IsClientScriptBlockRegistered(
this.GetType(), "alert"))
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("function doAlert() {");
sb.Append("alert ('welcome!'); }");
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(), "alert", sb.ToString(), true);
}

Ok, so that's a bit contrived. You're not going to use a StringBuilder for something that simple. But, with a more complicated script you probably would, especially if the script will be different depending on the state of the page.

I had a little Winforms utility sitting around I've been using for a while to automate that process for me, and I decided to put it up on the web. So, here you go: http://jdconley.com/stringbuildify. You feed the engine a multi-line string (like a script) and it gives you back a StringBuilder with everything properly escaped and such. The code's a bit of a mess at this second, that's why I didn't post it. Enjoy!

Wednesday, September 19, 2007

ASP.NET Centric Extensions

I've been loving the new Extension Methods in Orcas. If you haven't had the pleasure of working with them, well, you're really missing out. They are utility at its finest. Here is a list of my favorite extensions I've written thus far.

URLEncode

Ok, I know this sounds stupid, but for some reason I really hate typing HttpUtility.UrlEncode(myString) all over the place so I wrote this extension.

public static string UrlEncode(this string toEncode)
{
return HttpUtility.UrlEncode(toEncode);
}
ToHexString

For some reason my memory for .NET format strings really sucks. I blame Intellisense. I always forget how to hex encode integers. In case you're wondering, this is quite useful for giving users short strings that can be very unique and directly represent a primary key in a database. Hex is generally friendlier on the eyes than a number alone.

public static string ToHexString(this int val)
{
return string.Format("{0:x2}", val);
}

FromHexString

Likewise, when you have a hex encoded integer, it's quite useful to get that value back out. I have no idea why, but I forget this neat trick all the time and constantly have to look it up. No more!

public static int FromHexString(this string val)
{
return Convert.ToInt32(val, 16);
}
ToUrlString

I posted this a few weeks ago. I love it...

GetRootPath

This is something I have struggled with since the early days of ASP. There has never been a straightforward way to get the root of the URI of a request. This method extends HttpRequest and gives you the scheme (http/https) and host/port of the request. So, provided a request to http://jdconley.com/blog/ this extension would return http://jdconley.com

public static string GetRootPath(this HttpRequest r)
{
string hostPort = r.Url.Authority;
string scheme = r.Url.Scheme;

string path = string.Concat(scheme, @"://", hostPort);

return path;
}
GetConfigBasedPath

A common task in a web app is to send out an email for one reason or another. Maybe you want to send someone a link to a download, confirm their registration, or bug them when they haven't visited your site in a while. This method takes an application relative path stored in your App.Config appSettings and translates it into a fully qualified URL based on the current executing page. Your configuration might look like:

<add key="EventDetailsFormatString" value="~/events/{0}.aspx"/>

You could then use the following extension to turn that relative URL into a fully qualified based on the current page that is executing. It relies on the GetRootPath method above.

public static string GetConfigBasedPath(
this Page p,
string configKey,
params object[] formatArgs)
{
if (null == p)
throw new ArgumentNullException("p");

if (string.IsNullOrEmpty(configKey))
throw new ArgumentNullException("configKey");

string valFromConfig = ConfigurationManager.AppSettings.Get(configKey);

if (null == valFromConfig)
throw new ArgumentOutOfRangeException("configKey",
configKey,
"The specified key was not found in the configuration.");

Uri rootUri = new Uri(p.Request.GetRootPath(), UriKind.Absolute);

Uri relativeUri = new Uri(
p.ResolveUrl(string.Format(valFromConfig, formatArgs)),
UriKind.Relative);

Uri fullUri = new Uri(rootUri, relativeUri);

return fullUri.ToString();
}
IsDescendentOrSelfSelected

In a Treeview you often want to see if a node or any of its children are selected to help determine how to display the tree. This is especially useful when using the Treeview for navigation.

public static bool IsDescendantOrSelfSelected(this TreeNode node)
{
if (node.Selected)
{
return true;
}
else if (node.ChildNodes.Count > 0)
{
foreach (TreeNode n in node.ChildNodes)
{
if (IsDescendantOrSelfSelected(n))
return true;
}

return false;
}
else { return false;
}
}
EnsureVisible

This one is quite simple. It just makes sure that the provided node is visible in the tree by making sure all of its parent nodes are expanded. This is a useful function for stateless Treeviews or synchronizing the same Treeview across multiple pages.

public static void EnsureVisible(this TreeNode node)
{
if (null != node.Parent)
{
node.Parent.Expand();
EnsureVisible(node.Parent);
}
}

Well, that's it for now! I hope someone finds these as useful as I do.

Wednesday, September 5, 2007

List of Country Names

Today I had to build yet-another-list of country names for a web site drop down form. I did some Google searching for about 10 minutes and didn't find anything that was free. I dunno, maybe I'm just blind... I did, however, find a list on Wikipedia. So, I decided I'd pull it down and parse it up. Here's the code:

static void Main(string[] args)
{
string fullpage;
using (WebClient wc = new WebClient())
{
byte[] pageBytes = wc.DownloadData(
@"http://en.wikipedia.org/wiki/List_of_country_name_etymologies");
string encoding = wc.ResponseHeaders["Content-Encoding"];
fullpage = wc.Encoding.GetString(pageBytes);
}

MatchCollection countryMatches = Regex.Matches(fullpage,
@"<p><b><a href="". ""\s*title="". "">(?<country>. )</a></b>:</p>",
RegexOptions.Multiline);

foreach (Match m in countryMatches)
{
Group g = m.Groups["country"];
Console.WriteLine(g.Value);
}

Console.WriteLine("done");
Console.ReadLine();
}

As you can see it's dirt simple. Just a WebClient to download the page (holy crap it's big) and a simple Regex call. Presto! A list of countries I'll be dropping into a database so we can edit it later.

About the Author

JD Conley is an entrepreneur and hacker, currently working away his golden handcuffs at Playdom, a subsidiary of the Walt Disney Company, since Hive7 was acquired. We make social games. The views and opinions expressed on this post are his and do not necessarily represent or reflect those of The Walt Disney Company.