The story of Ruby and Python in Silverlight

04 May 2008

In early March, right around the time we shipped IronPython and IronRuby with support for Silverlight 2, Michael Foord wrote a email to the IronPython Mailing List expressing a very valid point-of-view on the dynamic language integration. I recently found the email, and it rattled me a bit. I dedicate this post to Michael, thanks for inspiring me to tell this story. http://lists.ironpython.com/pipermail/users-ironpython.com/2008-March/006559.html Here's the quote:
> This is instead of the previous model (fro Silverlight 1.1) 
> where assemblies and Python files would only be fetched 
> from the server if they were actually needed. If what 
> I have just said is true then the new model is *massively* 
> inferior in my opinion.
After fighting with Silverlight not to cut features, getting screwed over in every which way possible, but then in-fact being able to preserve the same development experience as SL1.1 had for dynamic languages while improving the deployment story, reading this felt like being back in those feature planning meetings when my precious features were getting cut. But Michael didn't do anything wrong, he's actually pointing out a totally different problem altogether; we don't communicate the "why" enough. There are very good reasons dynamic languages in Silverlight work the way they do, and I want to take this opportunity to tell you the story of how we got from Silverlight 1.1 Alpha to Silverlight 2.

Silverlight and synchronous downloading

This is a very rough topic, as users insist on the feature, and no one seems to listen. Just search for "silverlight synchronous downloading" and you'll see what I mean. =) Silverlight applications start running on the UI thread of the browser. This means that all downloads on the UI thread in Silverlight must happen asynchronously, as to not hang the browser. Though there is a way to access the JavaScript XmlHttpRequest object (which supports synchronous behavior) from managed code and dispatch downloads through that, it has limited functionality (no cross domain, sync download may hang browser, browser behavior may be different, etc, etc). Presumably you should be able to download synchronously from a background thread, but Silverlight doesn't support that today. =(  

Where did this XAP thing come from?

SL1.1 was very uncomplicated. Just files. SL2 comes around, and there's this XAP thing. WTF? Any dynamic languages require it too? WTF!?! Given the synchronous=nono mindset, the Silverlight team decided to create this XAP-package, so they didn't have to programmatically download dependencies on application startup (outside of resources in XAML). Also, you can argue that packaging up the entire application is better for startup and is nicer to web servers (keeps # of requests down). If you want to keep that initial XAP size small, you can programmatically download and load assemblies after.

This SUCKS for dynamic languages!

Trust me, this was my initial reaction. When I started in July, I became in charge of everything that is Silverlight and Dynamic Languages. I fought and fought to get features that would still allow for just-text deployment, but it all boiled down to cutting a feature all this depended on: synchronous download. Why is synchronous download important to the SL1.1 dynamic language model? Because we don't support continuations. In SL1.1 when we saw an "import" we triggered a synchronous download of the python module. This always bothered me since it's not what Python does, and we provided no way of changing it. To support this asynchronously we'd have to change import to have it trigger a callback when the download was done to continue on with the program. Uck! That's not python anymore, and changing the language is not an option. So, without asynchronous downloading, the old model was as good as dead.

How Ironic?

Another quote from the same email Michael sent:
> It is also ironic that Jim Hugunin and John Lam 
> (in Mix 07) made much of how the great advantages 
> of working with dynamic languages for Silverlight 
> is that they can be deployed as text. Now of course
> they need to be 'compiled' with a custom tool 
> (chiron) and deployed in this compiled form - 
> and not deployed as text...
Keep in mind, we always wanted to support XAP as a deployment model; we just didn't want it for development. So, the real question is "how do you use XAP for development but still keep the edit-refresh experience of a dynamic languages?" The answer: Chiron.

Just-text "resurrected"

When you develop a dynamic language app, your app is just text; there should be no xap files. The only place you'd see a reference to .xap is in your HTML file. Given a app folder structure that looked like this:
Foo/
   Index.html
   App/
      App.py

You'd see this in Index.html:
<param name="source" value="App.xap" />

This just means "You're start script (App.py, by default), is in the App directory". Run Chiron from your app's root directory like this:
>>> Chiron /w

Then navigate to http://localhost:2060, and whola! You're application is running! You can edit your files, hit refresh on the browser, and the app will update with your changes! The XAP is being generated in memory when it's requested and sent to the client, so you'll never see it on the file system. In the end, it all matters about the experience of developing your app, and this gives you the same effect as SL1.1 did.

Deployment: an upgrade from SL1.1

Also, now we support the package deployment model: simply run this in your app's root directory:
>>> Chiron /d:App /z:App.xap

App.xap will be generated with the contents of your /App folder, and now you can throw the application on any web server you'd like (and remove the /App folder if you choose). Note: though Chiron seems like a web-server, it's not; It only listens on localhost. This is slightly annoying when using 2 machines to develop your app (one to code it and one to view it), so that restriction will be lifted in the future. However, do not use Chiron for a deployment server! IIS or Apache are just fine. =)

My XAP is filled with CRAP!

Here's where the broken bottles should start flying at my face: App.xap is simply a zip file. If you extract it, you'll see your original application (from the /app folder down), but with some extra gunk:
Foo.xap/
       App.py
       AppManifest.xaml
       IronPython.dll
       IronPython.Modules.dll
       Microsoft.Scripting.dll
       Microsoft.Scripting.Silverlight.dll

Eek! Why all the DLLs!? Here's another dirty little secret; Silverlight doesn't ship with IronPython anymore! =( I can't blame this totally anyone else though, this was our call ... but for good reasons. We wanted to get the new language bits to you quicker than Silverlight releases. I'll even go as far as saying I want you to be able to take our sources and compile them for Silverlight. With IronPython in the box, that wouldn't have been very pleasant. Plus, being in a shipping Microsoft product comes with a ton of red tape, and we wanted to avoid that. Lastly, Microsoft is very concerned with intellectual property, and the lawyers still haven't figured out the "open source project that takes contributions from non-Microsoft employees that ships in a Microsoft product" scenario. Since we take contributions to IronRuby today, and will in the future for IronPython, the best bet was for us to get the hell out of the Silverlight runtime. Silverlight did give us some pressure though; we would have added more size to the Silverlight runtime. They are extremely concerned about size, and given the other issues we agreed to moving outside. However we still are part of the Silverlight development process, and we still stick our bits in the Silverlight SDK for some publicity. =)

Extensions to the rescue, NOT!

Keep in mind, this decision was made long before I joined the team, and long before XAP was a word you heard in the Silverlight hallways. At the time, Silverlight was designing a extension model, such that it didn't matter that you weren't in the core, end-user applications would automatically download "extensions" as needed. So, it seemed obvious that this new-fangled extension story was exactly what we wanted to do. In SL1.1, the DLR provided hooks that the XAML Parser called when <x:code source="app.py" /> was encountered. With the knowledge of SL2 moving to a code-first model, we planned on removing these hooks, and instead use the new Silverlight application model to host the DLR and call into script code first. From there, the app developer could load XAML. Then features started getting cut. And of course, extensions were one of them. WTF!? The DLR bet its entire existence in Silverlight on this being in! This was my first experience of how shipping software works at Microsoft; lots of planning, prioritizing, and scheduling. Slipping is very frowned upon, and extensions were looked upon as a threat to shipping SL2 (contrary to popular belief, Microsoft doesn't have unlimited resources =P). This, coupled with XAP, set me completely back to square one. Or so I thought. XAP saved our ass with not having to depend on synchronous download (which was cut around the same time). But not having the extension model was kind of annoying. This meant that we had to stick our assemblies inside the XAP. Sure, a XAP is cached by your browser, but every time you download a dynamic language app for the first time you have to download the language assemblies. That was a big bummer, and that's why the DLLs are in the XAP, by default. =P

Haha! Our feature works!

But as luck would have it, though the testing for extensions were cut, the fundamental feature actually exists in SL2; the ability to point your AppManifest.xaml file at assemblies outside the XAP. Since the code to load these assemblies goes through the Silverlight network stack, these assemblies could even be cross-domain! Awesome!! So, we discovered this, and quickly make Chiron understand this. So, if you look at Chiron.exe.config, you'll see an section with some interesting contents.
<!--
    This sets the base URL to load language assemblies from, instead of
    packaging them in the XAP. If omitted, assemblies are copied from
    localAssemblyPath and inserted in the XAP file.

    For a rooted path but a relative domain name:
    <add key="urlPrefix" value="/path/to/language/assemblies" />

    For an absolute URL on a domain:
    <add key="urlPrefix" value="http://example.com/assemblies/" />
    -->

    <!-- the location of language assemblies on disk -->
    <add key="localAssemblyPath" value="." />
Word of warning: this isn't supported by the Visual Studio Silverlight tools, so don't go off expecting this to work in a C#/VB app. It's this by design feature that needs more work to be prime-time. However, since Chiron understands how it works, we use it

Assemblies get the f*** out of my XAP

The "urlPrefix" option is your savior! By default, there is no urlPrefix, so Chiron will stick the assemblies into the XAP. But, if you use the option with a rooted path:
<add key="urlPrefix" value="/lib" />
The assemblies WILL NOT BE INCLUDED IN THE XAP! You're XAP will look like this:
App.xap/
       App.py
       AppManifest.xaml
You're application will look for the assemblies in the /lib folder of whatever server you put the application on. While running the app from Chiron. it will serve the assemblies from that directory "magically", so no extra work on your part during development. However if you deploy to http://foo.com, you need to make sure all the assemblies you need are located at http://foo.com/lib.

Assemblies, go as far away as possible

Lastly, if you want to host the assemblies on a completely different domain than your app, you can!
<add key="urlPrefix" value="http://bar.com/lib" />
This will grab the assemblies from http://bar.com/lib, but there's a catch. Silverlight will enforce the existence of a clientpolicy.xml or clientaccesspolicy.xml file on http://bar.com, with the following contents:
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="*">
      </allow-from>
      <grant-to>
        <resource path="/lib" subpaths="true" />
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>
This will give any app access to your /lib directory. You can tighten up the restrictions here (search for Silverlight clientaccesspolicy.xml, I'm sure you'll find what you need.), but this will work. Also, Silverlight respects Flash's clientpolicy.xml files. =) The beauty of having the DLLs outside the XAP is they will only be downloaded once, and cached in your browser! You're XAP will be small (around 2k, yes, the compression isn't great ... we're working on it). Actually, all the samples on http://dynamicsilverlight.net work this way, check it out if you don't believe me: http://dynamicsilverlight.net/see/ruby/clock. Fire up Firebug to see what gets downloaded. Note: some domains (like Microsoft.com) don't allow serving of DLLs. This is the main reason we didn't throw these DLLs on download.microsoft.com and enable the cross-domain download by default. Again, this is why this feature needs some more thought and isn't supported by VS tools.

Hey, it's not crap after all

So, dynamic languages in Silverlight let you:
  1. Develop with a text-editor with edit-refresh while running "Chiron /w"
  2. Deploy as a XAP file
  3. Have the Dynamic Language DLLs be served outside the XAP file.
This was the goal we had at the beginning of SL2 development, and though there were a few bumps in the road, screaming at meetings, cursing with my office door closed (I'm a New Yorker, what do you expect?), the aggravation paid off and we've met those goals.

... but there's still some crap

Granted, there is still a lot we can do to improve this.
  1. A XAP with 2 files in it is almost amusing. It really shouldn't be needed. We need to find a way to make it optional. This, as always, boils down to synchronous downloads need to be allowed on background threads. SL2 Beta1 doesn't even support asynchronous downloads on a background thread, but Beta2 will, largely due to my yelling, but not synchronous yet. That needs to get in so we can provide the option the XAP-less option.
  2. True extension support. The browser cache doesn't cut it. We need a GAC for Silverlight, where you can optionally install ahead of time, or download ONLY ONCE. This will allow versioning of those assemblies to be easier, and side-by-side version not just a matter of what URL it came from.

Don't agree with me?

Given all the things I've said, do you still have nits about dynamic languages in Silverlight? Please, let me know! I'm really a web developer acting as a Microsoft tools developer, so the littlest details piss me off until they are perfect. If you can convince me that something should change here, you can pretty much count on it getting into Silverlight. But that is if you can convince me ... I am stubborn. =P And on that note, one last quote from Michael:
> Feel free to pass my comments on to those who made these 
> decisions (I'm well aware it isn't you Dino)... On the 
> other hand I may be wrong (and would be both delighted 
> and apologetic).
I hope I proved you wrong =)
comments powered by Disqus