In 24 hours I installed Ubuntu, downloaded the latest version of mono, fixed a bug in it and submitted a pull request – the whole process was really simple and I want to let everybody else know how simple it is so they might be inspired to do the same.
In the beginning
I took the decision this week to join the growing number of .NET developers who choose to run Linux, not for any ideological reasons in particular – but just to experience this side of the fence for the first time in a few years, catch up on what has changed and to get my code working over here in Mono.
To that end, I installed Ubuntu 10.04, installed VirtualBox with Windows 7 and Visual Studio, and then proceeded to start building my existing projects in mono to see how they fared. The great thing about Ubuntu is that it comes with Mono, and I haven’t got to do anything to kick-start this process. (Ooh, flame bait here)
My process for building my .NET projects is simply to execute the solution file with xbuild like so
xbuild myAmazingProject.Sln
I started with my .NET 2.0 projects, and all but my largest work project built and executed on the version of Mono that ships with Ubuntu 10.04 (the current LTS) – that’s Mono 2.4 – this crashed the C# compiler in a number of places and therefore did not work.
In an effort to get this working I upgraded to Ubuntu 10.10 which gave me Mono 2.6.7, which fared better but still fell over in a number of other slightly different places.
I then decided to upgrade to 2.8, and found somebody who had set up a script to install a parallel version of Mono (without overwriting the system version – rather important for me as I was interested in not breaking my system through ignorance). This had the effect of getting my big failing work .NET 3.5 work project to build (On top of NH, SolrNet, Moq, MySQL, ASP.NET MVC etc – the usual suspects).
Great, now for RavenDB
RavenDB is always going to be an interesting one, it is written in C#4 on top of .NET 4.0 and uses a lot of the latest features in these technologies, the C#4 compiler hasn’t a hope of dealing with this, not even in 2.8 – and falls over with some fairly hairy messages.
There is no technical reason why RavenDB should not work on Linux, and I’m not starting my Mono experience trying to fix C# compiler issues – so I pop into my Windows Virtual Box and do a RavenDB build in Visual Studio via the standard build scripts.
The server is a massive piece of kit, and in the 2.8 environment I set up typing something like this
mono Raven.Server.exe /ram
Resulted in the square root of diddly squat occuring, asking mono to give me a verbose trace of what occurred with
mono Raven.Server.exe -v --trace /ram
This left me with a large trace and none the wiser about what really went wrong. Back to basics then, eating an elephant and all that.
public static void Main (string[] args)
{
using(var store = new EmbeddableDocumentStore(){
RunInMemory = true
})
{
store.Initialize();
}
}
This falls over with the useful error:
Unhandled Exception: System.MissingMemberException: The type being lazily initialized does not have a public, parameterless constructor.
at System.Threading.LazyInitializer.GetDefaultCtorValue[IStorageActionsAccessor] () [0x00000] in <filename unknown>:0
at System.Threading.ThreadLocal`1+<DataSlotCreator>c__AnonStorey42[Raven.Database.Storage.IStorageActionsAccessor].<>m__45 () [0x00000] in <filename unknown>:0
This is clearly a bug in ThreadLocal<T>, and it is at this point I stop and take stock of the situation I find myself in
Let me tell you, option #1 is looking really appealing right now, I am not a Linux user and have survived this far into the process by being a dumb person clicking on buttons in Ubuntu and things somehow “just working” (Parallel Mono 2.8 environment included in that).
After consultation on Twitter, I am assured that the Mono team are friendly and that I’ll get plenty of help on IRC if I need it. I decide to take the plunge and go for Option #2.
Running bleeding edge mono
sudo apt-get install irrssi
irssi
/connect irc.gimp.net
/join #mono
How I mine for fish?
I felt like an idiot for asking such an RTFM-answerable question, but thankfully as promised I got a friendly response pointing me to
http://www.mono-project.com/Parallel_Mono_Environments
http://mono-project.com/Compiling_Mono_From_Git
Not only two very good links (I’d not have found the first, I’m only now using the term “Parallel Mono Environments”) because I now know that’s what you call them, I also received good solid warnings about doing the right things in the right order to ensure that I built Mono to a secondary location and didn’t overwrite my Ubuntu version.
By following these two sets of instructions, it_just_worked, the process went something like this
vim ~/mono-git-environment
MONO_PREFIX=/opt/mono-git
GNOME_PREFIX=/usr
export DYLD_LIBRARY_FALLBACK_PATH=$MONO_PREFIX/lib:$DYLD_LIBRARY_FALLBACK_PATH
export LD_LIBRARY_PATH=$MONO_PREFIX/lib:$LD_LIBRARY_PATH
export C_INCLUDE_PATH=$MONO_PREFIX/include:$GNOME_PREFIX/include
export ACLOCAL_PATH=$MONO_PREFIX/share/aclocal
export PKG_CONFIG_PATH=$MONO_PREFIX/lib/pkgconfig:$GNOME_PREFIX/lib/pkgconfig
export PATH=$MONO_PREFIX/bin:$PATH
PS1="[mono] \w @
:wq!
source ~/mono-git-environment
git clone git://github.com/mono/mono.git
./autogen.sh –prefix=/opt/mono-git
make PROFILE=net_4_0 && make install PROFILE=net_4_0
There, I now have the latest version of mono and by typing
source ~/mono-git-environment
At any time, whatever I run using mono will use this version of mono! I must admit, I messed it up at first and forgot the PROFILE=net_4_0 bit that I need if I want .NET 4 (which I do), and then I pasted the above script for setting up the mono git environment wrong and missed out the first two characters (oops), but on figuring out that it just worked!
The best thing about this, in order to get whatever everybody has been working on lately, I just do
source ~/mono-git-environment
git pull
make PROFILE=net_4_0 && make install PROFILE=net_4_0
Isolating the bug
mono –v --trace bin/Debug/TestProject.exe > log.txt
This traces every method called during the process of executing my test project and gives me a giant file containing that information, I had to look up a vim cheatsheet for navigating and searching/paging through this mess.
This didn’t really give me a lot of information beyond the error I got when running it the first time, but it did give me a history of calls made and a complete stack trace at the point of crashing.
This led me to the following method in RavenDB
public void Batch(Action<IStorageActionsAccessor> action)
{
if (disposed)
{
Trace.WriteLine("TransactionalStorage.Batch was called after it was disposed, call was ignored.");
return; // this may happen if someone is calling us from the finalizer thread, so we can't even throw on that
}
if(current.Value != null)
{
action(current.Value);
return;
}
disposerLock.EnterReadLock();
try
{
Interlocked.Exchange(ref lastUsageTime, DateTime.Now.ToBinary());
using (tableStroage.BeginTransaction())
{
var storageActionsAccessor = new StorageActionsAccessor(tableStroage, uuidGenerator, DocumentCodecs);
current.Value = storageActionsAccessor;
action(current.Value);
tableStroage.Commit();
storageActionsAccessor.InvokeOnCommit();
onCommit();
}
}
finally
{
disposerLock.ExitReadLock();
current.Value = null;
}
}
Mmm, tasty - I know that the exception occurs when trying to access the .Value property of the ThreadLocal<ISomeInterface>, so I start with the first instance of it and write a test program
class MainClass
{
public static void Main (string[] args)
{
ThreadLocal<IPointlessActions> newLocal = new ThreadLocal<IPointlessActions>();
bool willThrowAnException = newLocal.Value == null;
}
}
public interface IPointlessActions
{
void DoSomething();
}
Verifying that it is a bug
Back to #mono on irc.gimp.net, I paste the above code example into pastebin, and link to the documentation for the class on MSDN.
We agree that it is probably a bug, and I get helpful feedback on where to start if I want to stand a chance of getting anything pulled into Mono (It’s a really trivial issue, and probably a really trivial fix, not worth the effort of pulling/merging anyway), I’m also advised to add a bugzilla for it on the mono site because that makes it easier to do changelogs.
This is all information that they’ve no doubt had to divulge numerous times to complete novices like myself, and I’m not getting a single snide remark or passive aggressive behaviour – very refreshing!
Fixing the bug
I add a bugzilla, and open two files in vim side by side, they are
mcs/class/corlib/System.Threading/ThreadLocal.cs
and
mcs/class/corlib/Test/System.Threading/ThreadLocalTests.cs
The first thing to do is write a test demonstrating the failing behaviour
[Test]
public void DefaultValueIsUsedIfNoInitializerSupplied()
{
ThreadLocal<IEnumerable> local = new ThreadLocal<IEnumerable>();
IEnumerable value = local.Value;
Assert.AreEqual(null, value);
}
I can then run the test by typing
make run-test FIXTURE=System.Threading.ThreadLocalTests PROFILE=net_4_0
On verifying the fail, I make the fix (passing in () => default(X) in the default constructor or something to that end), run the test again and push it up to github with
git commit –m “some fix message”
git push robashton
Running RavenDB
I do a build with
make PROFILE=net_4_0 && make install PROFILE=net_4_0
And re-run the in memory application, success – it works! I then check basic persistence
using(var store = new EmbeddableDocumentStore(){
RunInMemory = true
})
{
store.Initialize();
using(var session = store.OpenSession()){
session.Store(new {
Id = "Test/1",
FirstWord = "Hello",
SecondWorld = "World"
});
session.SaveChanges();
}
}
A direction for this
I have no timeline for the above, I'm deliberately saving the hardest till last - and I strongly suspect I'll be relying on others taking up the strain where I struggle there, but I've heard good things about turnaround times on compiler bugs so I'm not worried about that
With the support I've seen from the mono community I've contacted so far, I don't have any doubt that this can be made to work as it should, great stuff
2020 © Rob Ashton. ALL Rights Reserved.