<?xml version="1.0"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">

<!-- psst: http://www.metabrew.com/atom.xml -->

  <channel>
    <title>www.metabrew.com</title>
    <link>http://www.metabrew.com/</link>
    <atom:link href="http://www.metabrew.com/rss.xml" rel="self" type="application/rss+xml" />
    <description>stuff from www.metabrew</description>
    <language>en-us</language>
    <pubDate>Mon, 11 Jul 2011 15:01:01 BST</pubDate>
    <lastBuildDate>Mon, 11 Jul 2011 15:01:01 BST</lastBuildDate>

    
    <item>
      <title>BigWig: A better Erlang webtool (spawnfest entry)</title>
      <link>http://www.metabrew.com/article/bigwig-erlang-webtool-spawnfest</link>
      <pubDate>Mon, 11 Jul 2011 00:00:00 BST</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/bigwig-erlang-webtool-spawnfest</guid>
      <description>&lt;p&gt;This weekend, &lt;a href='http://twitter.com/mokele'&gt;Steve&lt;/a&gt;, &lt;a href='http://twitter.com/skarab'&gt;Hunter&lt;/a&gt;, &lt;a href='http://twitter.com/puzza007'&gt;Paul&lt;/a&gt; and I took part in &lt;a href='http://spawnfest.com/' target='new'&gt;Spawnfest&lt;/a&gt;, a 48-hour Erlang programming event.&lt;/p&gt;

&lt;p&gt;Our team name was SMELLS LIKE BEAM SPIRIT. Here we are on Sunday evening:&lt;/p&gt;
&lt;a href='/images/bigwig/team.jpg'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/team.jpg' title='Smells like BEAM spirit' width='500' /&gt;
&lt;/a&gt;
&lt;h2 id='what_a_bunch_of_tools'&gt;What a bunch of tools&amp;#8230;&lt;/h2&gt;

&lt;p&gt;The Erlang VM lets you inspect all aspects of a running node, examine running processes, explore process hierarchies, and profile running systems. This is typically done from the Erlang console, but if you&amp;#8217;re feeling especially nostalgic, you can start Erlang&amp;#8217;s appmon (a real GUI app), or &amp;#8220;webtool&amp;#8221;, a web-based version of some of the tools:&lt;/p&gt;

&lt;h3 id='erlangs_webtool_and_appmon'&gt;Erlang&amp;#8217;s Webtool and Appmon&lt;/h3&gt;
&lt;a href='/images/bigwig/webtool-orig.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/webtool-orig.png' title='Erlang&amp;apos;s Webtool' width='500' /&gt;
&lt;/a&gt;&lt;br /&gt;&lt;a href='/images/bigwig/appmon-orig.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/appmon-orig.png' title='Erlang&amp;apos;s Appmon, and other tools' width='500' /&gt;
&lt;/a&gt;
&lt;h2 id='bigwig'&gt;BigWig&lt;/h2&gt;

&lt;p&gt;Our project, called BigWig, is a modern suite of web-based tools that we hope will eventually supercede webtool, appmon and the rest.&lt;/p&gt;

&lt;h3 id='processes_etop'&gt;Processes (etop)&lt;/h3&gt;

&lt;p&gt;Our version of etop, a realtime-updating, sortable process list:&lt;/p&gt;
&lt;a href='/images/bigwig/etop.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/etop.png' title='Bigwig&amp;apos;s erlang process viewer' width='500' /&gt;
&lt;/a&gt;
&lt;h3 id='process_info'&gt;Process Info&lt;/h3&gt;

&lt;p&gt;Click on a process ID anywhere in the app, and you get a process info dialog, where you can see the state of the process, kill it and send messages:&lt;/p&gt;
&lt;a href='/images/bigwig/pid.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/pid.png' title='Process info dialog' width='500' /&gt;
&lt;/a&gt;
&lt;h3 id='application_explorer_appmon'&gt;Application Explorer (appmon)&lt;/h3&gt;

&lt;p&gt;Explore the application supervison-tree hierarchy in style. You can click around and more nodes are loaded in on-demand, and changes are sent to the page updating the graph as they happen:&lt;/p&gt;
&lt;a href='/images/bigwig/appmon.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/appmon.png' title='Bigwig&amp;apos;s application explorer' width='500' /&gt;
&lt;/a&gt;
&lt;h3 id='sasl_report_browser'&gt;SASL Report Browser&lt;/h3&gt;

&lt;p&gt;This one isn&amp;#8217;t quite finished yet, but is intended to be a nice way to browse SASL reports instead of report browser (rb.erl).&lt;/p&gt;

&lt;p&gt;We opted to read the existing SASL binary dir format, instead of installing our own event handler that logged reports differently. This was probably a mistake, since log_mf_h handler and rb.erl are pretty dated, and designed specifically to render reports as text to stdout. At the moment we do install a custom event handler, but only so we can stream new reports to the browser as they happen.&lt;/p&gt;

&lt;p&gt;We&amp;#8217;ll tidy it up, but ultimately I think we should be installing our own event handler and storing reports in a nicer way - possibly couch, or even just update the code to use disk_log, and separate out the rendering bits.&lt;/p&gt;

&lt;h3 id='dashboard'&gt;Dashboard&lt;/h3&gt;

&lt;p&gt;Shows the list of installed and loaded applications, which releases are installed, according to release_handler, and details about the VM.&lt;/p&gt;

&lt;p&gt;We ran out of time at the weekend, so you just see the JSON for now:&lt;/p&gt;
&lt;a href='/images/bigwig/dashboard.png'&gt;
    &lt;img alt='' class='aligncenter size-full' src='/images/bigwig/dashboard.png' title='Bigwig&amp;apos;s dashboard' width='500' /&gt;
&lt;/a&gt;
&lt;h2 id='bigwig_architecture'&gt;BigWig Architecture&lt;/h2&gt;

&lt;p&gt;We used &lt;a href='https://github.com/extend/cowboy' target='new'&gt;cowboy&lt;/a&gt; as our webserver, and make extensive use of websockets. For example, those sparkline graphs and metrics in the left sidebar are pushed to the page, and updated every couple of seconds.&lt;/p&gt;

&lt;p&gt;At the moment, BigWig only inspects the node it is currently running on. The plan is to make it start up as a hidden node, and let you specify which nodes to monitor by providing a nodename and cookie.&lt;/p&gt;

&lt;h2 id='source_code'&gt;Source Code&lt;/h2&gt;

&lt;p&gt;All on github here: &lt;a href='https://github.com/beamspirit/bigwig'&gt;BigWig for
Erlang&lt;/a&gt;. Licensed the same as Erlang/OTP.&lt;/p&gt;

&lt;p&gt;Check the readme - installing and running is easy!&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Erlang rebar tutorial: generating releases and upgrades</title>
      <link>http://www.metabrew.com/article/erlang-rebar-tutorial-generating-releases-upgrades</link>
      <pubDate>Sat, 26 Mar 2011 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/erlang-rebar-tutorial-generating-releases-upgrades</guid>
      <description>&lt;p&gt;During my experiments with rebar, I made a simple example app for testing upgrades and releases. This article will walk you through using rebar to create an application, lay it out properly, package and deploy it, and create and install new versions without downtime.&lt;/p&gt;

&lt;p&gt;The code accompanying this article is in various branches of &lt;a href='https://github.com/RJ/erlang_rebar_example_project'&gt;github.com/RJ/erlang_rebar_example_project&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; The &lt;a href='http://www.erlang.org/doc/design_principles/des_princ.html'&gt;OTP
Design Principles&lt;/a&gt; docs are a good place to start if you want an overview of the OTP approach to Erlang apps and releases. However, rebar isn&amp;#8217;t (yet) part of OTP, so consider that background reading. Rebar makes things much easier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id='creating_the_project'&gt;Creating the project&lt;/h2&gt;

&lt;p&gt;Build rebar:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd&lt;/span&gt; ~/src
&lt;span class='gp'&gt;$&lt;/span&gt; git clone https://github.com/basho/rebar.git
&lt;span class='go'&gt;Initialized empty Git repository in /tmp/rebar/.git/&lt;/span&gt;
&lt;span class='go'&gt;remote: Counting objects: 2651, done.&lt;/span&gt;
&lt;span class='go'&gt;remote: Compressing objects: 100% (1344/1344), done.&lt;/span&gt;
&lt;span class='go'&gt;remote: Total 2651 (delta 1540), reused 2227 (delta 1174)&lt;/span&gt;
&lt;span class='go'&gt;Receiving objects: 100% (2651/2651), 622.99 KiB | 495 KiB/s, done.&lt;/span&gt;
&lt;span class='go'&gt;Resolving deltas: 100% (1540/1540), done.&lt;/span&gt;
&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd &lt;/span&gt;rebar &lt;span class='o'&gt;&amp;amp;&amp;amp;&lt;/span&gt; make
&lt;span class='go'&gt;...snip....&lt;/span&gt;
&lt;span class='go'&gt;==&amp;gt; rebar (compile)&lt;/span&gt;
&lt;span class='go'&gt;Congratulations! You now have a self-contained script called &amp;quot;rebar&amp;quot; in&lt;/span&gt;
&lt;span class='go'&gt;your current working directory. Place this script anywhere in your path&lt;/span&gt;
&lt;span class='go'&gt;and you can use rebar to build OTP-compliant apps.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now we&amp;#8217;ll make a project directory called &amp;#8220;dummy_proj&amp;#8221;, copy rebar into it, and use rebar to generate a skeleton application:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; mkdir -p ~/src/dummy_proj/apps
&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd&lt;/span&gt; ~/src/dummy_proj/
&lt;span class='gp'&gt;$&lt;/span&gt; cp ../rebar/rebar .
&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd &lt;/span&gt;apps
&lt;span class='gp'&gt;$&lt;/span&gt; ../rebar create-app &lt;span class='nv'&gt;appid&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;dummy_proj
&lt;span class='go'&gt;==&amp;gt; dummy_proj (create-app)&lt;/span&gt;
&lt;span class='go'&gt;Writing src/dummy_proj.app.src&lt;/span&gt;
&lt;span class='go'&gt;Writing src/dummy_proj_app.erl&lt;/span&gt;
&lt;span class='go'&gt;Writing src/dummy_proj_sup.erl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To the skeleton, I added a basic gen_server called dummy_proj_server, which just keeps track of the number of times it was poked, i.e. it holds some state, for demonstration purposes.&lt;/p&gt;

&lt;p&gt;I also renamed dummy_proj_app.erl to just dummy_proj.erl, and added a start/0 function, which is useful when starting the application during developement, when not running from a generated release.&lt;/p&gt;

&lt;h3 id='compiling_with_rebar'&gt;Compiling with rebar&lt;/h3&gt;

&lt;p&gt;You need a rebar.conf, place this in the top-level project directory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;sub_dirs&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;
            &lt;span class='s'&gt;&amp;quot;apps/dummy_proj&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
            &lt;span class='s'&gt;&amp;quot;rel&amp;quot;&lt;/span&gt;
           &lt;span class='p'&gt;]}.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;erl_opts&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;debug_info&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;fail_on_warning&lt;/span&gt;&lt;span class='p'&gt;]}.&lt;/span&gt;

&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;require_otp_vsn&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;R14&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And now to compile, you do:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar compile
&lt;span class='go'&gt;==&amp;gt; dummy_proj (compile)&lt;/span&gt;
&lt;span class='go'&gt;Compiled src/dummy_proj_sup.erl&lt;/span&gt;
&lt;span class='go'&gt;Compiled src/dummy_proj.erl&lt;/span&gt;
&lt;span class='go'&gt;Compiled src/dummy_proj_server.erl&lt;/span&gt;
&lt;span class='go'&gt;==&amp;gt; rel (compile)&lt;/span&gt;
&lt;span class='go'&gt;==&amp;gt; dummy_proj (compile)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that you now have .beam files in apps/dummy_proj/ebin/, and the .app.src generated apps/dummy_proj/ebin/dummy_proj.app for you, with a complete modules list.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; I made a simple Makefile that calls &amp;#8216;rebar compile&amp;#8217;, because I&amp;#8217;m too used to typing make. Find it in the git repo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id='running_your_app_development'&gt;Running your app (development)&lt;/h3&gt;

&lt;p&gt;Here&amp;#8217;s how you can start the application (and sasl, for nice error reporting):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; erl -pa apps/*/ebin -boot start_sasl -s dummy_proj
&lt;span class='go'&gt;...snip...&lt;/span&gt;
&lt;span class='go'&gt;=INFO REPORT==== 16-Mar-2011::14:17:04 ===&lt;/span&gt;
&lt;span class='go'&gt;Starting dummy_proj application...&lt;/span&gt;

&lt;span class='go'&gt;=PROGRESS REPORT==== 16-Mar-2011::14:17:04 ===&lt;/span&gt;
&lt;span class='go'&gt;          supervisor: {local,dummy_proj_sup}&lt;/span&gt;
&lt;span class='go'&gt;             started: [{pid,&amp;lt;0.45.0&amp;gt;},&lt;/span&gt;
&lt;span class='go'&gt;                       {name,dummy_proj_server},&lt;/span&gt;
&lt;span class='go'&gt;                       {mfargs,{dummy_proj_server,start_link,[]} },&lt;/span&gt;
&lt;span class='go'&gt;                       {restart_type,permanent},&lt;/span&gt;
&lt;span class='go'&gt;                       {shutdown,5000},&lt;/span&gt;
&lt;span class='go'&gt;                       {child_type,worker}]&lt;/span&gt;

&lt;span class='go'&gt;=PROGRESS REPORT==== 16-Mar-2011::14:17:04 ===&lt;/span&gt;
&lt;span class='go'&gt;         application: dummy_proj&lt;/span&gt;
&lt;span class='go'&gt;          started_at: nonode@nohost&lt;/span&gt;
&lt;span class='go'&gt;Eshell V5.8.1  (abort with ^G)&lt;/span&gt;
&lt;span class='go'&gt;1&amp;gt; dummy_proj_server:num_pokes().&lt;/span&gt;
&lt;span class='go'&gt;0&lt;/span&gt;
&lt;span class='go'&gt;2&amp;gt; dummy_proj_server:poke().     &lt;/span&gt;
&lt;span class='go'&gt;{ok,1}&lt;/span&gt;
&lt;span class='go'&gt;3&amp;gt; dummy_proj_server:poke().&lt;/span&gt;
&lt;span class='go'&gt;{ok,2}&lt;/span&gt;
&lt;span class='go'&gt;4&amp;gt; dummy_proj_server:num_pokes().&lt;/span&gt;
&lt;span class='go'&gt;2&lt;/span&gt;
&lt;span class='go'&gt;5&amp;gt;  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you have a nice sensibly structured Erlang project that you can compile with rebar. Exit the VM with q(). and let&amp;#8217;s use rebar to package it up, so you can deploy it on a production box.&lt;/p&gt;

&lt;h2 id='generating_your_first_release'&gt;Generating your first release&lt;/h2&gt;

&lt;p&gt;When you generate a release with rebar, and indeed if you use the erlang tools manually (not recommended, just use rebar), you end up with the whole Erlang VM and required libraries packaged up under one directory.&lt;/p&gt;

&lt;p&gt;This means you have a self-contained environment containing Erlang, the OTP libraries you need, and all your application code and dependencies. You can just tar it up, ship it over to another machine (of the same architecture, eg GNU/Linux 64-bit), and run it there.&lt;/p&gt;

&lt;h3 id='creating_a_node_config'&gt;Creating a node config&lt;/h3&gt;

&lt;p&gt;Use rebar to create a default node configuration in a rel subdirectory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; mkdir rel
&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd &lt;/span&gt;rel/
&lt;span class='gp'&gt;$&lt;/span&gt; ../rebar create-node &lt;span class='nv'&gt;nodeid&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;dummynode
&lt;span class='go'&gt;==&amp;gt; rel (create-node)&lt;/span&gt;
&lt;span class='go'&gt;Writing reltool.config&lt;/span&gt;
&lt;span class='go'&gt;Writing files/erl&lt;/span&gt;
&lt;span class='go'&gt;Writing files/nodetool&lt;/span&gt;
&lt;span class='go'&gt;Writing files/dummynode&lt;/span&gt;
&lt;span class='go'&gt;Writing files/app.config&lt;/span&gt;
&lt;span class='go'&gt;Writing files/vm.args&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You need to edit reltool.config a little; point to to your apps directory, and make sure the version number matches your .app.src file. You should also add dummy_app to the list of applications that are started as part of the release. Here&amp;#8217;s &lt;a href='https://github.com/RJ/erlang_rebar_example_project/blob/v1/rel/reltool.config' target='v1rel'&gt;reltool.conf from my v1 tag&lt;/a&gt;&lt;/p&gt;

&lt;h3 id='generating_the_release'&gt;Generating the release&lt;/h3&gt;

&lt;p&gt;Back in the top level directory, just run:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate
&lt;span class='go'&gt;==&amp;gt; rel (generate)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now have a look in rel/dummynode. This is the release directory containing everything you need to run your application.&lt;/p&gt;

&lt;p&gt;We are going to be creating more releases later, so rename rel/dummynode to rel/dummynode_first, and then launch it using the handy script that rebar created for us:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='nb'&gt;cd &lt;/span&gt;rel/dummynode_first
&lt;span class='gp'&gt;$&lt;/span&gt; ./bin/dummynode console
&lt;span class='go'&gt;...snip...&lt;/span&gt;
&lt;span class='go'&gt;Erlang R14B (erts-5.8.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:5] [hipe] [kernel-poll:true&lt;/span&gt;
&lt;span class='go'&gt;=INFO REPORT==== 16-Mar-2011::13:29:59 ===&lt;/span&gt;
&lt;span class='go'&gt;Starting dummy_proj application...&lt;/span&gt;
&lt;span class='go'&gt;Eshell V5.8.1  (abort with ^G)&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)1&amp;gt; &lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)1&amp;gt; dummy_proj_server:num_pokes().&lt;/span&gt;
&lt;span class='go'&gt;0&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)2&amp;gt; dummy_proj_server:poke().     &lt;/span&gt;
&lt;span class='go'&gt;{ok,1}&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)3&amp;gt; dummy_proj_server:poke().&lt;/span&gt;
&lt;span class='go'&gt;{ok,2}&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)4&amp;gt; dummy_proj_server:num_pokes().&lt;/span&gt;
&lt;span class='go'&gt;2&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)5&amp;gt; &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now the release is running, we never want to have to restart it ever again, so open up another console because we want to leave that running whilst we work on version 2.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; In a production environment, you would start with &amp;#8220;./bin/dummynode start&amp;#8221; so it runs in the background, and use &amp;#8220;dummynode attach&amp;#8221; to get a console.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check the &lt;a href='https://github.com/RJ/erlang_rebar_example_project/blob/v1/'&gt;'v1 branch'&lt;/a&gt; on github for code up to this point.&lt;/p&gt;

&lt;h2 id='upgrading_to_version_2'&gt;Upgrading to Version 2&lt;/h2&gt;

&lt;p&gt;Add the poke_twice() function to dummy_proj_server.&lt;/p&gt;

&lt;p&gt;Change the version from &amp;#8220;1&amp;#8221; to &amp;#8220;2&amp;#8221;, in both apps/dummy_proj.app.src and rel/reltool.conf.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the github &lt;a href='https://github.com/RJ/erlang_rebar_example_project/compare/v1...v2#diff-3'&gt;diff between v1...v2&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Erlang application version numbers can be any string - I tend to use a date format with letter: &amp;#8220;20110316a&amp;#8221;, but you can use any scheme you want. I tag releases in git with the same version as the erlang application. We&amp;#8217;ll just use &amp;#8220;1&amp;#8221;, &amp;#8220;2&amp;#8221;, &amp;#8220;3&amp;#8221; here for simplicity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; If you use &lt;code&gt;{vsn, git}&lt;/code&gt; as the version in your .app.src, rebar will get the version string from the closest git tag.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now build the new version:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar compile
&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;So now you have rel/dummy_proj, containing a full release (VM included) of version 2. If you don&amp;#8217;t care about online-upgrades, you could just kill your version 1 VM, and start version 2 from this new release directory.&lt;/p&gt;

&lt;h3 id='writing_the_appup_upgrade_instructions'&gt;Writing the .appup upgrade instructions&lt;/h3&gt;

&lt;p&gt;In order to make an upgrade, you must have a valid .appup file. This tells the erlang release_handler how to upgrade and downgrade between specific versions of your application.&lt;/p&gt;

&lt;p&gt;Rebar has a (relatively new) command called &amp;#8216;generate-appups&amp;#8217;. I&amp;#8217;ll show how it works, but ultimately we&amp;#8217;ll write our .appup manually, and keep it in our project directory (in git).&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate-appups &lt;span class='nv'&gt;previous_release&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;dummynode_first
&lt;span class='go'&gt;==&amp;gt; rel (generate-appups)&lt;/span&gt;
&lt;span class='go'&gt;Generated appup for dummy_proj&lt;/span&gt;
&lt;span class='go'&gt;Appup generation complete&lt;/span&gt;
&lt;span class='gp'&gt;$&lt;/span&gt; cat ./rel/dummynode/lib/dummy_proj-2/ebin/dummy_proj.appup
&lt;span class='gp'&gt;%&lt;/span&gt;% appup generated &lt;span class='k'&gt;for &lt;/span&gt;dummy_proj by rebar &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;2011/03/16 13:37:43&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;¬                                   
&lt;span class='go'&gt;{&amp;quot;2&amp;quot;, [{&amp;quot;1&amp;quot;, [{update,dummy_proj_server,{advanced,[]}}]}], [{&amp;quot;1&amp;quot;, []}]}.¬&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Get rid of the autogenerated one, and create the appup file manually, in apps/dummy_proj/ebin/dummy_proj.appup:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
    &lt;span class='c'&gt;%% Upgrade instructions from 1 to 2&lt;/span&gt;
    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nb'&gt;load_module&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;dummy_proj_server&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;    
    &lt;span class='p'&gt;]}],&lt;/span&gt; 
    &lt;span class='c'&gt;%% Downgrade instructions from 2 to 1&lt;/span&gt;
    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;1&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nb'&gt;load_module&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;dummy_proj_server&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;    
    &lt;span class='p'&gt;]}]&lt;/span&gt;
&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This .appup contains instructions for upgrading and downgrading between versions &amp;#8220;2&amp;#8221; and &amp;#8220;1&amp;#8221;. Typically the downgrade instructions are the reverse of the upgrade instructions. Since we just added a function to our server process, without changing any internal state, we can just use load_module instructions. The Appup Cookbook explains the various upgrade instructions in depth.&lt;/p&gt;

&lt;p&gt;Now generate again, overwriting the previous version 2. This will just make sure the .appup is part of the release directory:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate -f
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And now, create the upgrade package:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate-upgrade &lt;span class='nv'&gt;previous_release&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;dummynode_first
&lt;span class='go'&gt;==&amp;gt; rel (generate-upgrade)&lt;/span&gt;
&lt;span class='go'&gt;dummynode_2 upgrade package created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The generate-upgrade command will look for rel/dummynode as the current version, and rel/dummynode_first as the previous version. It should have created the upgrade .tar.gz in rel:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ls -lh rel/
&lt;span class='go'&gt;total 15M&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x 8 rj rj 4.0K 2011-03-16 13:42 dummynode&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x 8 rj rj 4.0K 2011-03-16 13:29 dummynode_first&lt;/span&gt;
&lt;span class='go'&gt;-rw-r--r-- 1 rj rj  14M 2011-03-16 13:45 dummynode_2.tar.gz&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x 2 rj rj 4.0K 2011-03-16 13:11 files&lt;/span&gt;
&lt;span class='go'&gt;-rw-r--r-- 1 rj rj  922 2011-03-16 13:36 reltool.config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='installing_the_upgrade_package'&gt;Installing the upgrade package&lt;/h3&gt;

&lt;p&gt;You should still have the VM running from dummynode_first. Make sure you called poke(), so the internal state is something other than the default. This will help illustrate that the upgrade worked seamlessly.&lt;/p&gt;

&lt;p&gt;Copy the upgrade package to the releases directory of the running release:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; cp rel/dummynode_2.tar.gz rel/dummynode_first/releases
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now, at the Erlang console where version 1 is running, we use release_handler to check which releases are currently available, and install our new one:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;(dummynode@127.0.0.1)5&amp;gt; release_handler:which_releases().&lt;/span&gt;
&lt;span class='go'&gt;[{&amp;quot;dummynode&amp;quot;,&amp;quot;1&amp;quot;,[],permanent}]&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)6&amp;gt; release_handler:unpack_release(&amp;quot;dummynode_2&amp;quot;).&lt;/span&gt;
&lt;span class='go'&gt;{ok,&amp;quot;2&amp;quot;}&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)7&amp;gt; release_handler:install_release(&amp;quot;2&amp;quot;).&lt;/span&gt;
&lt;span class='go'&gt;{ok,&amp;quot;1&amp;quot;,[]}   &lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)8&amp;gt; dummy_proj_server:num_pokes().&lt;/span&gt;
&lt;span class='go'&gt;2&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)9&amp;gt; dummy_proj_server:poke_twice().&lt;/span&gt;
&lt;span class='go'&gt;{ok,4}&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)10&amp;gt; dummy_proj_server:num_pokes(). &lt;/span&gt;
&lt;span class='go'&gt;4&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)11&amp;gt; release_handler:which_releases().&lt;/span&gt;
&lt;span class='go'&gt;[{&amp;quot;dummynode&amp;quot;,&amp;quot;2&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;  [&amp;quot;kernel-2.14.1&amp;quot;,&amp;quot;stdlib-1.17.1&amp;quot;,&amp;quot;dummy_proj-2&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;   &amp;quot;sasl-2.1.9.2&amp;quot;,&amp;quot;compiler-4.7.1&amp;quot;,&amp;quot;crypto-2.0.1&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;   &amp;quot;syntax_tools-1.6.6&amp;quot;,&amp;quot;edoc-0.7.6.7&amp;quot;,&amp;quot;et-1.4.1&amp;quot;,&amp;quot;gs-1.5.13&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;   &amp;quot;hipe-3.7.7&amp;quot;,&amp;quot;inets-5.5&amp;quot;,&amp;quot;mnesia-4.4.15&amp;quot;,&amp;quot;observer-0.9.8.3&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;   &amp;quot;public_key-0.8&amp;quot;,&amp;quot;runtime_tools-1.8.4.1&amp;quot;,&amp;quot;ssl-4.0.1&amp;quot;,&lt;/span&gt;
&lt;span class='go'&gt;   &amp;quot;tools-2.6.6.1&amp;quot;,&amp;quot;webtool-0.8.7&amp;quot;,&amp;quot;wx-0.98.7&amp;quot;,&amp;quot;xmerl-1.2.6&amp;quot;],&lt;/span&gt;
&lt;span class='go'&gt;  current},&lt;/span&gt;
&lt;span class='go'&gt; {&amp;quot;dummynode&amp;quot;,&amp;quot;1&amp;quot;,[],permanent}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The upgrade worked; you can see that the num_pokes() was preserved, and that the new poke_twice() function is available.&lt;/p&gt;

&lt;p&gt;release_handler shows our version 2 as &amp;#8220;current&amp;#8221;, and the original version 1 as &amp;#8220;permanent&amp;#8221;. This means that although version 2 is running right now, if you restart the VM, version &amp;#8220;1&amp;#8221; will be booted up.&lt;/p&gt;

&lt;p&gt;If you are happy with the upgrade, make it permanent, meaning it will boot instead of version 1 if you restart the VM:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;(dummynode@127.0.0.1)12&amp;gt; release_handler:make_permanent(&amp;quot;2&amp;quot;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Check the &lt;a href='https://github.com/RJ/erlang_rebar_example_project/blob/v2/'&gt;'v2 branch'&lt;/a&gt; on github for code up to this point.&lt;/p&gt;

&lt;h2 id='version_3_and_beyond'&gt;Version 3 and beyond&lt;/h2&gt;

&lt;p&gt;The upgrade from v1 to v2 was simple: we just added a fun without changing the internal #state{} record.&lt;/p&gt;

&lt;p&gt;Erlang .appup files can do all sorts of clever stuff, allowing you to rewire your running applications during the upgrade process.&lt;/p&gt;

&lt;p&gt;The &lt;a href='http://www.erlang.org/doc/design_principles/appup_cookbook.html'&gt;Appup
Cookbook&lt;/a&gt; details the various commands you can put in your .appup.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s do an upgrade with a more complex appup - we&amp;#8217;ll change the #state record in the dummy_proj_server process.&lt;/p&gt;

&lt;p&gt;For version 3, we&amp;#8217;ll track prods as well as pokes, which will require another field in the state record.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the github &lt;a href='https://github.com/RJ/erlang_rebar_example_project/compare/v2...v3#diff-2'&gt;diff between v2...v3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out the addition to .appup for this release:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;3&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
    &lt;span class='c'&gt;%% Upgrade instructions&lt;/span&gt;
    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;update&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;dummy_app_server&lt;/span&gt;&lt;span class='p'&gt;,{&lt;/span&gt;&lt;span class='n'&gt;advanced&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='n'&gt;from2to3&lt;/span&gt;&lt;span class='p'&gt;]}}&lt;/span&gt;
    &lt;span class='p'&gt;]}],&lt;/span&gt; 
    &lt;span class='c'&gt;%% Downgrade instructions&lt;/span&gt;
    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;update&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;dummy_app_server&lt;/span&gt;&lt;span class='p'&gt;,{&lt;/span&gt;&lt;span class='n'&gt;advanced&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='n'&gt;from3to2&lt;/span&gt;&lt;span class='p'&gt;]}}&lt;/span&gt;
    &lt;span class='p'&gt;]}]&lt;/span&gt;
&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This {update..} directive will result in the code_change function being called on the dummy_app_server. The purpose of code_change is to change the State from the old (v2) format, to the new (v3) format.&lt;/p&gt;

&lt;p&gt;Although it&amp;#8217;s not strictly necessary, I pass &amp;#8216;from2to3&amp;#8217; as the &amp;#8216;Extra&amp;#8217; field in the code_change call. This can be pattern matched on, and makes it clear in your code_change code exactly what version upgrade is expected.&lt;/p&gt;

&lt;h3 id='packaging_and_upgrading_to_v3'&gt;Packaging and upgrading to v3&lt;/h3&gt;

&lt;p&gt;Move the generated release dir for v2:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; mv rel/dummynode rel/dummynode_2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Compile and generate for v3, then create the upgrade package:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar compile
&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate
&lt;span class='gp'&gt;$&lt;/span&gt; ./rebar generate-upgrade &lt;span class='nv'&gt;previous_release&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;dummynode_2
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; You need to provide the full, standalone generated release dir as the previous_release, you can&amp;#8217;t use dummynode_first, even though that contains version 2 of your release.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As before, copy the upgrade package to the releases directory of the running release:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; cp rel/dummynode_3.tar.gz rel/dummynode_first/releases
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now, at the Erlang console where you upgraded v1 to v2:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;(dummynode@127.0.0.1)12&amp;gt; release_handler:unpack_release(&amp;quot;dummynode_3&amp;quot;).&lt;/span&gt;
&lt;span class='go'&gt;{ok,&amp;quot;3&amp;quot;}&lt;/span&gt;
&lt;span class='go'&gt;(dummynode@127.0.0.1)13&amp;gt; release_handler:install_release(&amp;quot;3&amp;quot;).&lt;/span&gt;
&lt;span class='go'&gt;{ok,&amp;quot;2&amp;quot;,[]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='congratulations'&gt;Congratulations&lt;/h3&gt;

&lt;p&gt;Now you can deploy hot-code-upgrades the proper OTP way. Ideal for complex or large upgrades that change internal state or do require special upgrade hooks. Read the Appup Cookbook a few times, and &lt;strong&gt;test your upgrade packages in a staging environment before deploying&lt;/strong&gt;. You can just tar up and copy the live environment to your staging box, to get an exact clone of the production system to test upgrades against.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning: a current issue with downgrades&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To downgrade, you just install a previous release. However, there is currently a bug where release_handler chokes during downgrades to the first version, because of a discrepancy in the naming of .boot files in the release. release_handler has start.boot hardcoded, but rebar will generate appname.boot, with start.boot as a symlink. If you need to do downgrades, test this carefully before deploying; you may need to manually rename the boot file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id='cowboying_out_quick_fixes'&gt;Cowboying out quick fixes&lt;/h2&gt;

&lt;p&gt;Appup files and generating releases is rather heavyweight. Here&amp;#8217;s an overview of the process I&amp;#8217;m using on &lt;a href='https://www.irccloud.com/'&gt;IRCCloud&lt;/a&gt; at the moment&lt;/p&gt;

&lt;h4 id='complex_upgrades_are_proper_releases_with_appup'&gt;Complex upgrades are proper releases, with .appup&lt;/h4&gt;

&lt;p&gt;No way around it; bit of a pain to create and test, but glorious when you pull off a complex upgrade with zero downtime. Releases are tagged in git with the datetime and letter version, eg: v20110324a. If I do a second release that day, v20110324b.&lt;/p&gt;

&lt;h4 id='hotfixes_preserve_my_sanity'&gt;&amp;#8216;Hotfixes&amp;#8217; preserve my sanity&lt;/h4&gt;

&lt;p&gt;If I make a quick fix that simply requires reloading a module with no risk of instability, I do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reset code to currently deployed tag, egv 20110324a&lt;/li&gt;

&lt;li&gt;Write the fix&lt;/li&gt;

&lt;li&gt;Commit as v200110324a-hotfix1&lt;/li&gt;

&lt;li&gt;Build this version of the specific module that&amp;#8217;s changed, and copy it into the production environment, eg to: /somewhere/dummyapp/lib/dummyapp-20110324a/ebin/&lt;/li&gt;

&lt;li&gt;Reload the module, eg by using l(module_name). at the shell.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It&amp;#8217;s of vital importance to have a repeatable process, so you know exactly which version of code (ie, the git tag) is currently running in production. If you can&amp;#8217;t be sure, then it&amp;#8217;s much harder to write successful upgrade code later on.&lt;/p&gt;

&lt;p&gt;This process gives a reasonable balance between periodic &amp;#8216;proper&amp;#8217; releases, during which any complex changes are made that rewire internal state, and quick fixes that just require a module reload.&lt;/p&gt;

&lt;h2 id='pitfalls_to_avoid'&gt;Pitfalls to avoid&lt;/h2&gt;

&lt;p&gt;In the IRCCloud app, there&amp;#8217;s one place that I don&amp;#8217;t use a supervisor, but wish I had; the user process acts as a sort of supervisor for connection processes, because I needed exponential backoff / more control over restarting crashed children.&lt;/p&gt;

&lt;p&gt;Don&amp;#8217;t do that. release_handler isn&amp;#8217;t aware of the child processes I spawn myself, so I can&amp;#8217;t use the normal appup process to call code_change.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Erlang/OTP releases: rebar, release_handler, .appup, etc</title>
      <link>http://www.metabrew.com/article/erlangotp-releases-rebar-release_handler-appup-etc</link>
      <pubDate>Sat, 18 Sep 2010 00:00:00 BST</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/erlangotp-releases-rebar-release_handler-appup-etc</guid>
      <description>&lt;p&gt;I&amp;#8217;ve been building something in Erlang recently, provisionally called &lt;a href='https://irccloud.com/' target='_blank' title='It&amp;apos;s the Gmail of IRC, honest!'&gt;IRCCloud.com&lt;/a&gt; (mention this post if you request an invite!) - it&amp;#8217;s an in-browser IRC client that stays connected for you all the time, so you never miss the conversation. You can reopen your browser later and still have all the backlog. &lt;a href='http://www.metabrew.com/article/how-we-use-irc-at-lastfm' target='_blank' title='IRC: still damn useful'&gt;IRC is damn useful&lt;/a&gt;, and &lt;a href='http://james.wheare.org/' target='_blank' title='James Wheare: International man of mystery'&gt;James&lt;/a&gt; and I are building IRCCloud to give you the advantages of IRC bouncer-esque functionality, with the ease of just opening a webpage.&lt;/p&gt;

&lt;p&gt;The Erlang backend is connected to various IRC servers on behalf of our users, so it&amp;#8217;s critical that I can deploy new versions of the app without restarting. Erlang&amp;#8217;s capability to do live upgrades to running applications means it&amp;#8217;s possible to deploy new versions of the backend without disconnecting everybody. I&amp;#8217;ve done a fair amount of Erlang before, but I&amp;#8217;ve only recently managed to get proper live-updates to running OTP applications working.&lt;/p&gt;

&lt;p&gt;Previous upgrade experience ranged from just copying over a new beam and running &lt;code&gt;l(my_module).&lt;/code&gt; in the shell, to manually triggering code_change like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='nn'&gt;sys&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;suspend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;module&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;my_module&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;code&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;load_file&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;my_module&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
&lt;span class='nn'&gt;sys&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;change_code&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;my_module&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;undefined&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
&lt;span class='nn'&gt;sys&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;resume&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to start doing complex updates that change internal state, amongst other things, you really want to be doing it in the proper OTP way.&lt;/p&gt;

&lt;p&gt;I started with this &lt;a href='http://spawnlink.com/articles/performing-real-time-upgrades-to-an-otp-system/' target='_blank' title='A good read'&gt;excellent series of articles about Erlybank, on spawnlink.com&lt;/a&gt;. I definitely recommend following along the last couple in the series to get a feel for packaging up erlang releases. I&amp;#8217;m not going to provide a step-by-step tutorial in this post, since Mitchell already did a great job in those articles.&lt;/p&gt;

&lt;p&gt;Unfortunately I fell at the last hurdle - I ran into the same problem as Ricardo in the comments, namely that &lt;a href='http://spawnlink.com/articles/performing-real-time-upgrades-to-an-otp-system/#comments' target='_blank'&gt;release_hander was looking for the .appup file in the wrong place&lt;/a&gt;. I don&amp;#8217;t know why this happens, but it seems that because I had the erlang system itself as a &amp;#8220;release&amp;#8221; (R13B etc), that somehow screwed things up. I found one other &lt;a href='http://erlang.2086793.n4.nabble.com/install-release-1-always-returns-enoent-error-td2093926.html' target='_blank' title='Annoying.'&gt;post about the same problem&lt;/a&gt;. If you know what&amp;#8217;s going on here, please leave a comment and put me out of my misery.&lt;/p&gt;

&lt;h2 id='upgrade_to_r14'&gt;Upgrade to R14!&lt;/h2&gt;

&lt;p&gt;Let me get this out the way first: &lt;strong&gt;upgrade your Erlang to R14 right now&lt;/strong&gt;, since R13B has a bug that prevents release_handler from figuring out which modules need updating when you install a new release. I lost some time to that, although consequently I did end up taking a nice tour of the sasl/release_handler/systools code.&amp;lt;/p&amp;gt; Oh, and also &amp;#8220;rebar generate&amp;#8221; (mentioned later in this post) will fail with some distro packaged versions of R13B04, with a message like: &lt;em&gt;ERROR: Unable to generate spec: read file info /usr/lib/erlang/man/man5/modprobe.d.5 failed.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id='first_target_system'&gt;First Target System&lt;/h2&gt;

&lt;p&gt;So I decided to roll my own &lt;a href='http://www.erlang.org/doc/system_principles/create_target.html' target='_blank'&gt;First Target System&lt;/a&gt;. The fine manual says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Often it is not desirable to use an Erlang/OTP system as is. A developer may create new Erlang/OTP compliant applications for a particular purpose, and several original Erlang/OTP applications may be irrelevant for the purpose in question. Thus, there is a need to be able to create a new system based on a given Erlang/OTP system, where dispensable applications are removed, and a set of new applications that are included in the new system. Documentation and source code is irrelevant and is therefore not included in the new system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another advantage of creating your own target system is that you get a guaranteed environment to deploy your code into - you can simply untar your system onto any machine (of the same platform/architecture) and it will run just like it ran on your test machine. Same exact version of Erlang, same exact libraries, same config.&lt;/p&gt;

&lt;p&gt;After following along with the manual, I had a shiny new target system - but much to my dismay I had a R13B directory under releases/, and trying to do an application upgrade resulted in the same problem as before: release_handler was still looking for an appup file under releases/R13B/ instead of releases/myapp/.&lt;/p&gt;

&lt;p&gt;I tried copying my .appup to R13B, and creating a valid blank appup, to no avail.  So..&lt;/p&gt;

&lt;h2 id='rebar_to_the_rescue_sort_of'&gt;Rebar to the rescue&amp;#8230; sort of&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://bitbucket.org/basho/rebar/wiki/Home'&gt;Rebar&lt;/a&gt; is an erlang build tool with a few nifty tricks (thanks &lt;a href='http://dizzyd.com/' target='_blank' title='Buy this man a pint.'&gt;Dave&lt;/a&gt; and the gang). Documentation/examples are still a little sparse, but one thing it does have in spades is a way to build Erlang target systems in one command: &lt;em&gt;./rebar generate&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To use rebar to make a target system you&amp;#8217;ll need a working OTP style application in the &lt;a href='http://erlang.org/doc/design_principles/applications.html#id69069' target='_blank' title='OTP Design principles: follow them!'&gt;usual layout&lt;/a&gt; (src,priv,ebin,include) with a valid .app file. Here are the &lt;a href='http://bitbucket.org/basho/rebar/wiki/ReleaseHandling' target='_blank'&gt;rebar release handling docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Okay. So now we have a nice target system (courtesy of rebar); your rel/&amp;#60;appname&amp;#62; directory will look like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;drwxr-xr-x  2 rj rj   21 2010-09-15 02:49 bin&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x  8 rj rj   70 2010-09-15 02:48 erts-5.8&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x  2 rj rj   37 2010-09-15 02:49 etc&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x 27 rj rj 4096 2010-09-15 02:49 lib&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x  3 rj rj   17 2010-09-15 02:49 log&lt;/span&gt;
&lt;span class='go'&gt;drwxr-xr-x  3 rj rj   43 2010-09-15 02:48 releases&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The lib/ directory contains stdlib, sasl, kernel, your application and any other deps you specified; The releases/ directory is reassuring empty; bin/ contains a handy nodetool script to start and stop your app; sasl is configured to log to a file. Rebar just saved you a few hours of fiddling to get things up and running. But you&amp;#8217;re not out of the woods yet..&lt;/p&gt;

&lt;h2 id='deploying_updates_to_rebarpackaged_target_system'&gt;Deploying updates to rebar-packaged target system&lt;/h2&gt;

&lt;p&gt;This was the tricky non-obvious bit. Rebar was great at getting the first system with my app deployed, however it doesn&amp;#8217;t really offer much to help you install an update to your running system.&lt;/p&gt;

&lt;p&gt;I wrote a script &lt;strong&gt;regen.sh&lt;/strong&gt; to use instead of &amp;#8220;rebar generate&amp;#8221;, which post-processes the directory structure rebar generates, to make it more amenable to deploying upgrades:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;!/bin/bash
&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;This will nuke rel/irccloud and regenerate using rebar.. [enter to continue]&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;read&lt;/span&gt;
rm -rf rel/irccloud
&lt;span class='nb'&gt;set&lt;/span&gt; -e
./rebar compile
./rebar generate
&lt;span class='nb'&gt;cd &lt;/span&gt;rel/irccloud/lib/
&lt;span class='nb'&gt;echo&lt;/span&gt; -n &lt;span class='s2'&gt;&amp;quot;Unpacking .ez files&amp;quot;&lt;/span&gt;
&lt;span class='k'&gt;for &lt;/span&gt;f in *.ez
&lt;span class='k'&gt;do&lt;/span&gt;
&lt;span class='nb'&gt;echo&lt;/span&gt; -n &lt;span class='s2'&gt;&amp;quot;.&amp;quot;&lt;/span&gt;
unzip &lt;span class='nv'&gt;$f&lt;/span&gt; &amp;amp;gt; /dev/null
rm &lt;span class='nv'&gt;$f&lt;/span&gt;
&lt;span class='k'&gt;done&lt;/span&gt;
&lt;span class='nb'&gt;echo&lt;/span&gt;
&lt;span class='nb'&gt;cd&lt;/span&gt; ../releases/
&lt;span class='c'&gt;# Get the version of the only release in the system, our new app:&lt;/span&gt;
&lt;span class='nv'&gt;VER&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='sb'&gt;`&lt;/span&gt;find . -maxdepth 1 -type d | grep -vE &lt;span class='s1'&gt;&amp;#39;^\.$&amp;#39;&lt;/span&gt; | head -n1 | sed &lt;span class='s1'&gt;&amp;#39;s/^\.\///g&amp;#39;&lt;/span&gt;&lt;span class='sb'&gt;`&lt;/span&gt;
&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Ver: ${VER}, renaming .rel + .boot files correctly&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;cd&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;${VER}&amp;quot;&lt;/span&gt;
mv irccloud.boot start.boot
mv irccloud.rel &lt;span class='s2'&gt;&amp;quot;irccloud-${VER}.rel&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;cd&lt;/span&gt; ../../../
&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This does three important things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unpack the &lt;em&gt;.ez files (zip files of applications in lib/), since upgrades with release_handler didn&amp;#8217;t work otherwise;&lt;/em&gt;&lt;/li&gt;

&lt;li&gt;rename irccloud.boot to start.boot, &lt;a href='http://erldocs.com/R14A/sasl/systools.html?i=5&amp;amp;search=systools#make_tar/1' target='_blank' title='Erldocs.com is great'&gt;since systools has &quot;start.boot&quot; hardcoded&lt;/a&gt; as the correct name of this file for packages;&lt;/li&gt;

&lt;li&gt;rename irccloud.rel to irccloud-&amp;#60;VERSION&amp;#62;.rel, since this is the standard layout for future packages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;NB: I think I also had to change the bin/irccloud script to -boot with ﻿&lt;code&gt;$RUNNER_BASE_DIR/releases/$APP_VSN/start&lt;/code&gt; instead of &lt;code&gt;$RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2 id='tooling_up_for_easier_releases'&gt;Tooling up for easier releases&lt;/h2&gt;

&lt;p&gt;Now in the top level directory (alongside rel,src,ebin..) I made a releases/ directory, from which I package up new versions ready for deployment.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m using a bash script, and some erlang, to facilitate packaging up new releases. Note that it adds, using erl -pz, the paths to the current .app file and beams, and the previous .app file and beams.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;#!/bin/bash&lt;/span&gt;
&lt;span class='nb'&gt;set&lt;/span&gt; -e
&lt;span class='nv'&gt;ERL_ROOT&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;$1&lt;/span&gt;
&lt;span class='nv'&gt;OLDVER&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;$2&lt;/span&gt;
&lt;span class='nv'&gt;VER&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;$3&lt;/span&gt;
&lt;span class='nb'&gt;cd&lt;/span&gt; ..
./rebar compile
&lt;span class='nb'&gt;cd&lt;/span&gt; -
&lt;span class='nv'&gt;OLDEBIN&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;${ERL_ROOT}/lib/irccloud-${OLDVER}/ebin/&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Fetching previous rel file: irccloud-${OLDVER}.rel from ${ERL_ROOT}/releases/${OLDVER}&amp;quot;&lt;/span&gt;
cp &lt;span class='s2'&gt;&amp;quot;${ERL_ROOT}/releases/${OLDVER}/irccloud-${OLDVER}.rel&amp;quot;&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;irccloud-${OLDVER}.rel&amp;quot;&lt;/span&gt;

erl -pz ../ebin/ -pz &lt;span class='s2'&gt;&amp;quot;${OLDEBIN}&amp;quot;&lt;/span&gt; -pz ../deps/*/ebin -noshell &lt;span class='se'&gt;\&lt;/span&gt;
-run release_helper go irccloud &lt;span class='nv'&gt;$VER&lt;/span&gt; ../ebin &lt;span class='nv'&gt;$OLDVER&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;${OLDEBIN}&amp;quot;&lt;/span&gt; &lt;span class='se'&gt;\&lt;/span&gt;
| grep -v &lt;span class='s1'&gt;&amp;#39;Source code not found&amp;#39;&lt;/span&gt;

&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Release ${VER} packaged&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id='release_helpererl'&gt;release_helper.erl&lt;/h3&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='c'&gt;% Check some stuff, write the .rel file, generate boot scripts and relup, make tar&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;release_helper&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;go&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;RELAPPS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;  &lt;span class='n'&gt;kernel&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;stdlib&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;sasl&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;crypto&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;ssl&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;inets&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;public_key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;compiler&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;syntax_tools&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;edoc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;eunit&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;xmerl&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='n'&gt;epgsql&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                    &lt;span class='n'&gt;mochiweb&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='nf'&gt;appver&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_atom&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class='nn'&gt;application&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Version of &amp;#39;&lt;/span&gt;&lt;span class='si'&gt;~p&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;..&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;value&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_,&lt;/span&gt; &lt;span class='nv'&gt;Ret&lt;/span&gt;&lt;span class='p'&gt;}}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;lists&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;keysearch&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;application&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;loaded_applications&lt;/span&gt;&lt;span class='p'&gt;()),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~p~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Ret&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nv'&gt;Ret&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;check_appfile_version&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;File&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AppName&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ExpectedVer&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Verifying .app file contains correct version..&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;application&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AppName&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Props&lt;/span&gt;&lt;span class='p'&gt;}]}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;file&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;consult&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;File&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nn'&gt;proplists&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;get_value&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;vsn&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Props&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
    &lt;span class='nv'&gt;ExpectedVer&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;ok&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='nv'&gt;FoundVer&lt;/span&gt;    &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;FAIL&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;App file contains ver: &lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt; but expected: &lt;/span&gt;&lt;span class='si'&gt;~s~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                             &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;FoundVer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ExpectedVer&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
                   &lt;span class='n'&gt;halt&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;go&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Args&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; 
  &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Ebin&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;PVersion&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;PEbin&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Args&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;release_helper running for: &lt;/span&gt;&lt;span class='si'&gt;~p&lt;/span&gt;&lt;span class='s'&gt;, oldversion: &lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;, new version: &lt;/span&gt;&lt;span class='si'&gt;~s~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
            &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;PVersion&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='n'&gt;ok&lt;/span&gt;        &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;check_appfile_version&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ebin&lt;/span&gt; &lt;span class='o'&gt;++&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;++&lt;/span&gt; &lt;span class='nv'&gt;Name&lt;/span&gt; &lt;span class='o'&gt;++&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;.app&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                                    &lt;span class='nb'&gt;list_to_atom&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nv'&gt;Erts&lt;/span&gt;      &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;erlang&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='nb'&gt;system_info&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;version&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nv'&gt;Vsn&lt;/span&gt;       &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;-&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nv'&gt;PrevVsn&lt;/span&gt;   &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;-&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;PVersion&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;version: &amp;#39;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;&amp;#39;&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nv'&gt;AppVers&lt;/span&gt;   &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;appver&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;)}&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;A&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='no'&gt;?RELAPPS&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
  &lt;span class='nv'&gt;Rel&lt;/span&gt;       &lt;span class='o'&gt;=&lt;/span&gt; 
&lt;span class='s'&gt;&amp;quot;{release, &lt;/span&gt;
&lt;span class='s'&gt;    {&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='s'&gt;, &lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='s'&gt;}, &lt;/span&gt;
&lt;span class='s'&gt;    {erts, &lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='s'&gt;}, &lt;/span&gt;
&lt;span class='s'&gt;    [   {&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;, &lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='s'&gt;},&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;        &amp;quot;&lt;/span&gt;
    &lt;span class='o'&gt;++&lt;/span&gt;
    &lt;span class='nn'&gt;string&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;join&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;{&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;, &lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\&amp;quot;&lt;/span&gt;&lt;span class='s'&gt;}&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;AV&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;AV&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;AppVers&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt; 
                &lt;span class='s'&gt;&amp;quot;,&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;        &amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='o'&gt;++&lt;/span&gt;
&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;    ]&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;}.&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='nv'&gt;RelText&lt;/span&gt;   &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Rel&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Erts&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;list_to_atom&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='nv'&gt;Version&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nv'&gt;Relfile&lt;/span&gt;   &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;.rel&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Vsn&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Writing &lt;/span&gt;&lt;span class='si'&gt;~s~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Relfile&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Fs&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;  &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;file&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;open&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Relfile&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Fs&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RelText&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[]),&lt;/span&gt;
  &lt;span class='nn'&gt;file&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;close&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Fs&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;make_script(&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;)..&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Vsn&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
  &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;systools&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_script&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Vsn&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nv'&gt;PVersion&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt; 
    &lt;span class='s'&gt;&amp;quot;0&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Previous version 0, not generating relup!&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[]);&lt;/span&gt;
    &lt;span class='p'&gt;_&lt;/span&gt;   &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;systools&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_relup&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Vsn&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;PrevVsn&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;PrevVsn&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;systools&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_tar&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Vsn&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
  &lt;span class='n'&gt;halt&lt;/span&gt;&lt;span class='p'&gt;().&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='making_a_second_release'&gt;Making a second release&lt;/h2&gt;
&lt;p&gt;Here's how I currently make a new release that can be deployed with a live-upgrade:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;Fix bugs, change some stuff, add features etc;&lt;/li&gt;
&lt;li&gt;Update ebin/irccloud.app to increase the version number, update the modules list if needed;&lt;/li&gt;
&lt;li&gt;Create/modify ebin/irccloud.appup to tell release_handler how to upgrade from the previous version to this new version. (See: Appup Cookbook);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cd releases/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./release_helper.sh &quot;../rel/irccloud&quot; &quot;1&quot; &quot;2&quot;&lt;/code&gt; # (where &quot;1&quot; was the version of the first release using rebar generate, &quot;2&quot; is the new one we want to package)&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Now I have irccloud-2.tar.gz in the releases/ directory, ready to be deployed. My packaging scripts also tag the release in git and a few other things I've omitted for clarity.&lt;/p&gt;&lt;h2&gt;Deploying the release&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;$ cp irccloud-2.tar.gz ../rel/irccloud/releases&lt;/code&gt; # or copy to whereever you put your target system on the production box instead of ../rel/irccloud&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erl&amp;gt; release_handler:unpack_release(&quot;irccloud-2&quot;).&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erl&amp;gt; release_handler:install_release(&quot;2&quot;).&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erl&amp;gt; release_handler:which_releases().&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;erl&amp;gt; release_handler:make_permanent(&quot;2&quot;).&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Step 3 does the upgrade, and runs the appup stuff - hope you tested on your development rig before deploying to production!&lt;br /&gt;
If you wrote your .appup properly, it is also safe to downgrade - just do &lt;code&gt;release_helper:install_release(&quot;1&quot;).&lt;/code&gt; after step 3.&lt;/p&gt;&lt;p&gt;Note, you must run erl with &quot;-boot start_sasl&quot; to use release_manager. I ran this from the same shell as &quot;bin/irccloud console&quot; which already had sasl running.&lt;/p&gt;&lt;h2&gt;Mochiweb caveat&lt;/h2&gt;&lt;p&gt;I'm using the excellent &lt;a href='http://github.com/mochi/mochiweb' target='_blank' title='Mochiweb: I love it.'&gt;Mochiweb HTTP library&lt;/a&gt; in my application. Here's the child specification from my top level supervisor:&lt;/p&gt;&lt;pre lang='erlang'&gt;{irccloud_web,
    {irccloud_web, start, [WebConfig]},
     permanent, 5000, worker, dynamic}&lt;/pre&gt;&lt;p&gt;Note that it says 'dynamic' in place of the modules list. Here's what the &lt;a href='http://erldocs.com/R14A/stdlib/supervisor.html'&gt;fine manual&lt;/a&gt; says:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Modules&lt;/strong&gt; is used by the release handler during code replacement to determine which processes are using a certain module. As a rule of thumb Modules should be a list with one element [Module], where Module is the callback module, if the child process is a supervisor, gen_server or gen_fsm. If the child process is an event manager (gen_event) with a dynamic set of callback modules, Modules should be dynamic. See OTP Design Principles for more information about release handling.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;What it doesn't say is that release_handler will timeout and screw your install because it can't find the list of modules when dynamic is specified, unless you answer correctly.&lt;/p&gt;&lt;p&gt;Here's how to appease release_handler if you have a dynamic modules list: &lt;a href='http://github.com/RJ/mochiweb/commit/931c5fb769be844c307a51596898ca6c55998219'&gt;http://github.com/RJ/mochiweb/commit/931c5fb769be844c307a51596898ca6c55998219&lt;/a&gt;&lt;/p&gt;&lt;h2&gt;In Conclusion&lt;/h2&gt;&lt;p&gt;In general, this was a rather painful experience.&lt;/p&gt;&lt;p&gt;What I'd like to be able to do is something like this:&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&quot;rebar make_tar newver=&amp;lt;newversion&amp;gt;&quot;&lt;/li&gt;
&lt;li&gt;version in the .app file is automatically incremented for me, modules list is updated. (open $EDITOR for confirmation/additional tweaks)&lt;/li&gt;
&lt;li&gt;Unless I manually wrote it already, .appup is automatically populated with {load_module, Mod} for each module that changed since last version was packaged, opened in $EDITOR so I can tweak/review.&lt;/li&gt;
&lt;li&gt;irccloud-&amp;lt;newver&amp;gt;.tar.gz is created&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Maybe I've missed something, and there's a much easier way to package subsequent releases with rebar, or something else I've overlooked.&lt;br /&gt;
How do you package and deploy your Erlang applications to do live-updates?&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Rewriting Playdar: C++ to Erlang, massive savings</title>
      <link>http://www.metabrew.com/article/rewriting-playdar-c-to-erlang-massive-savings</link>
      <pubDate>Wed, 21 Oct 2009 00:00:00 BST</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/rewriting-playdar-c-to-erlang-massive-savings</guid>
      <description>&lt;p&gt;
I've heard many anecdotes and claims about how many lines of code are saved when you write in Erlang instead of [C++/other language]. I'm happy to report that I now have first-hand experience and some data to share.
&lt;/p&gt;&lt;p&gt;
I initially wrote Playdar in C++ (using Boost and Asio libraries), starting back in February this year. I was fortunate to be working with some experienced developers who helped me come to terms with C++. There were three of us hacking on it regularly up until a few months ago, and despite being relatively new to C++, I'll say that we ended up with a well designed and robust codebase, all things considered.
&lt;/p&gt;&lt;h2&gt;On Feeling Smug&lt;/h2&gt;&lt;p&gt;
I'll admit I felt rather smug making it all work in C++ with Boost and ASIO. Getting it to build on all three platforms and dynamically load extensions (DLLs etc) at runtime in a cross-platform way was also quite satisfying (I had plenty of help with that side of things).  I learned a lot about C++, Boost, ASIO and CMake. But, as the codebase grew, I began to seriously question my decision to use C++. 
&lt;/p&gt;&lt;p&gt;
My initial reasons for choosing C++ were twofold:
&lt;ul&gt;
&lt;li&gt;Distribution - shipping the Erlang VM didn't sound like fun&lt;/li&gt;
&lt;li&gt;&lt;a href='http://developer.kde.org/~wheeler/taglib.html' target='taglib'&gt;Taglib&lt;/a&gt;  - *the* library to read metadata from audio files (mp3, m4a, ogg etc) is C++&lt;/li&gt;
&lt;/ul&gt;
It turns out Playdar is naturally a good fit for Erlang - it does lots in parallel, and lots of stuff it does is asynchronous  and event based. Even with all the stuff you get with Boost, multithreaded stuff in C++ is inelegant, to put it kindly.
&lt;/p&gt;&lt;h2&gt;SLOCed and Loaded&lt;/h2&gt;&lt;p&gt;
Anyway, a couple of weeks ago I sat down to re-implement Playdar from scratch in Erlang. I thrashed out the guts of it in a couple of days, and by the end of the week I almost had it 1:1 features with the C++ codebase. There's still a bit of C++ left - code to interface with taglib.

Using the SLOCcount tool (SLOC=source lines of code) I counted the lines of code in various modules from both codebases, here are the results:
&lt;br /&gt;
&lt;style type='text/css'&gt;
#matrix td{ font-size:90%; vertical-align:top; padding: 3px; } #matrix tr { background: #f0f0f0; } #matrix tr.odd { background: #ddd; }
#matrix td.b {font-size:100%; font-weight:bold;}
&lt;/style&gt;
&lt;table border='0' id='matrix'&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class='b' /&gt;
&lt;td class='b'&gt;Erlang Version&lt;/td&gt;
&lt;td class='b'&gt;C++ Version&lt;/td&gt;
&lt;td class='b'&gt;Savings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='b'&gt;Core Daemon&lt;/td&gt;
&lt;td&gt;1,100&lt;/td&gt;
&lt;td&gt;4,491&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='b'&gt;Library + Scanner&lt;/td&gt;
&lt;td&gt;197 + 167.cpp&lt;/td&gt;
&lt;td&gt;1,355&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='b'&gt;LAN Resolver&lt;/td&gt;
&lt;td&gt;105&lt;/td&gt;
&lt;td&gt;427&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='b'&gt;P2P&lt;/td&gt;
&lt;td&gt;463&lt;/td&gt;
&lt;td&gt;1,762&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd b'&gt;
&lt;td class='b'&gt;TOTAL&lt;/td&gt;
&lt;td&gt;&lt;em&gt;2,032&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;8,035&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;75%&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;strong&gt;
75% less lines of code using Erlang compared to C++ to implement the same thing - not too shabby :)
&lt;/strong&gt;
The second time around writing in Erlang I knew exactly what I was building, so it's unfair to compare development time of the two codebases, but given how fast I can type I reckon I saved a good few hours of just pounding the keyboard to input the code (and countless hours of debugging: Erlang tends to work first time, really). Well I'm not sure if &quot;saved&quot; is the right word, considering It was working in C++ already, but it's my time to waste :)
&lt;/p&gt;&lt;p&gt;
If you count the third party code bundled with both codebases (excluding boost/asio!) then the erlang codebase saves a whopping 92%. I'm more interested in the savings in code I had to write, however.
&lt;/p&gt;&lt;h2&gt;Memory and CPU Usage&lt;/h2&gt;&lt;p&gt;
I've done some preliminary comparisons between both projects, when it comes to CPU and memory usage both projects are pretty similar. The Erlang codebase uses slightly more memory than C++ at the moment, but I'm convinced I can get that down to at least as low as the C++ project was. I picked up a few optimization tricks from my three-part &lt;a href='http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1/'&gt;Million-user comet experiment&lt;/a&gt; in Erlang earlier this year. I'll post more about this if I learn any new tricks.
&lt;/p&gt;&lt;p&gt;
One thing I've realised about the Erlang codebase is that I've used processes to encapsulate state (active queries, specifically)  where I didn't really need to. It seemed sensible at the time, but it's probably just a waste of memory. I'm going to change it to spawn processes to get the work done (ie, a process that runs the query) but not necessarily just to maintain state.
&lt;/p&gt;&lt;h2&gt;Distribution to the desktop&lt;/h2&gt;&lt;h3&gt;C++&lt;/h3&gt;&lt;p&gt;
You just have to make sure that you build everything and ship with any DLLs along with checks in the installer for system libraries needed (runtime dlls). Oh, and make sure you don't change the plugin binary interface in the main app, or new plugins will crash and burn when you load them. Add a check for that. Oh and be careful about compiling taglib and stuff with mingw and the rest with VC++, or things might mysteriously crash. Also I heard a horror story about allocating memory in plugin code but deallocating it in the main app when the plugin was compiled against a different stdlib than the main app. This is all par for the course, and the experienced C++ developers I asked for help had no trouble making it work. &lt;strong&gt;Size of installable pacakge: 2.5MB&lt;/strong&gt;
&lt;/p&gt;&lt;h3&gt;Erlang&lt;/h3&gt;&lt;p&gt;
Compiling, and building/loading plugins in the Erlang codebase is straightforward on all platforms, as is often the way with VMs. I was against shipping the Erlang VM originally because I figured it would be a lot of hassle and increase the download size substantially. Packaging an Erlang app for the desktop involves taking the installed VM directory structure and stripping out all the docs, source and parts of the Erlang stdlib we don't use, then packaging it along with the compiled Playdar code. &lt;a href='http://couchdb.apache.org/' target='cdb'&gt;CouchDB&lt;/a&gt; does something like this too, and &lt;a href='http://www.rabbitmq.com/' target='rabbit'&gt;RabbitMQ&lt;/a&gt; ships the Erlang VM without stripping unneeded libs. We'll work on packaging some more (for all platforms), but to date &lt;a href='http://twitter.com/mxcl'&gt;Max&lt;/a&gt; has crafted a package that contains the necessary bits of the Erlang VM, a sexy Prefpane to start/stop the daemon on OS X, and the compiled Playdar code all &lt;strong&gt;weighing in under 10MB.&lt;/strong&gt;
&lt;/p&gt;&lt;p&gt;
We'll put together a Windows installer soon that'll probably be around the same size. A 10MB download isn't so bad nowadays, and I expect we can optimize the packaging process some more. Linux users will get a package that depends on the erlang VM in their package manager.
Seems like shipping Erlang apps to the desktop isn't so hard after all.
&lt;/p&gt;&lt;h2&gt;tl;dr&lt;/h2&gt;&lt;p&gt;
Someone rewrote a C++ app in Erlang: 75% less lines of code for same functionality.
&lt;/p&gt;&lt;p&gt;
You should read this &lt;a href='http://musicmachinery.com/2009/10/18/playing-with-playdar/'&gt;blog post about Playdar, by Paul Lamere&lt;/a&gt;,  and take a look at the &lt;a href='http://www.playdar.org/'&gt;Playdar website&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
&lt;a href='http://github.com/RJ/playdar'&gt;C++ codebase (deprecated)&lt;/a&gt;
&lt;a href='http://github.com/RJ/playdar-core'&gt;Erlang codebase&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;strong&gt;Playdar is the future, and the future is written in Erlang :)&lt;/strong&gt;
&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>Erlang talk at London Hackspace</title>
      <link>http://www.metabrew.com/article/erlang-talk-at-london-hackspace</link>
      <pubDate>Thu, 08 Oct 2009 00:00:00 BST</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/erlang-talk-at-london-hackspace</guid>
      <description>&lt;p&gt;Last night I gave an &amp;#8220;Intro to Erlang&amp;#8221; talk at a London Hackspace meetup. I did a quick audience survey first: About 75% did &amp;#8220;web programming&amp;#8221; (ruby,python,php,etc).  Around 30% admitted to regularly using C/C++/Java or desktop/mobile app development.  Less than 10% had much experience with functional programming.&lt;/p&gt;

&lt;p&gt;I wanted to impress upon the audience that Erlang is a practical language, built by Ericsson with a specific purpose in mind. You use Erlang to build useful, scalable and reliable distributed systems in the real world. This was worth pointing out because when many people hear &amp;#8220;functional programming&amp;#8221; they immediately think of eccentric bearded academics proving the validity of their Haskell code and comparing Monads.&lt;/p&gt;

&lt;p&gt;I skipped through the basics of sequential programming in Erlang pretty quickly and tried to spend most of the time showing how you handle processes and send messages. I built a basic Erlang server process that kept a count of how many operations it had done, explaining how it passes state to itself on every loop. Hopefully this helped some people grok how you can build servers that keep a global state by using recursion. I also showed off hot code reloading. We added another feature to the server and upgraded it without stopping it.&lt;/p&gt;

&lt;p&gt;You can download the code I used (see link at the end) if you want to try out the examples from last night yourself. The last code I showed was an example of doing the same thing using gen_server, so hopefully if you followed along you&amp;#8217;ll have a good understanding of what gen_server is and why it exists. &lt;h2&gt;Hot code reloading example&lt;/h2&gt; I can&amp;#8217;t write a post about Erlang without including some code, so here&amp;#8217;s the basic example I used showing how hot code reloading works:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ex09&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;client&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='nf'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
 &lt;span class='k'&gt;receive&lt;/span&gt;
   &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Client&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;double&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class='nv'&gt;Client&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
     &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

   &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Client&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;square&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class='nv'&gt;Client&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
     &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

   &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Client&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class='nv'&gt;Client&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='n'&gt;wtf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
     &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

   &lt;span class='n'&gt;reload&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Reloading&lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
     &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

   &lt;span class='n'&gt;stats&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
     &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Ops: &lt;/span&gt;&lt;span class='si'&gt;~p&lt;/span&gt;&lt;span class='s'&gt;, Wtfs: &lt;/span&gt;&lt;span class='si'&gt;~p&lt;/span&gt;&lt;span class='s'&gt; &lt;/span&gt;&lt;span class='si'&gt;~n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
     &lt;span class='n'&gt;loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Ops&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Wtfs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
 &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='c'&gt;% basic client API:&lt;/span&gt;

&lt;span class='nf'&gt;client&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Cmd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
 &lt;span class='nv'&gt;Pid&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;self&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt; &lt;span class='nv'&gt;Cmd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Num&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
 &lt;span class='k'&gt;receive&lt;/span&gt;
   &lt;span class='nv'&gt;Ans&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nv'&gt;Ans&lt;/span&gt;
 &lt;span class='k'&gt;after&lt;/span&gt; &lt;span class='mi'&gt;1000&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;timeout&lt;/span&gt;
 &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;And if you were following along you saw something like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;1&amp;gt; c(ex09).&lt;/span&gt;
&lt;span class='go'&gt;{ok,ex09}&lt;/span&gt;
&lt;span class='go'&gt;erl&amp;gt; Pid = ex09:start().&lt;/span&gt;
&lt;span class='go'&gt;&amp;lt;0.38.0&amp;gt;&lt;/span&gt;
&lt;span class='go'&gt;3&amp;gt; Pid ! stats.&lt;/span&gt;
&lt;span class='go'&gt;Ops: 0, Wtfs: 0&lt;/span&gt;
&lt;span class='go'&gt;stats&lt;/span&gt;
&lt;span class='go'&gt;4&amp;gt; ex09:client(Pid, double, 10).&lt;/span&gt;
&lt;span class='go'&gt;20&lt;/span&gt;
&lt;span class='go'&gt;5&amp;gt; ex09:client(Pid, triple, 10).&lt;/span&gt;
&lt;span class='go'&gt;wtf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;At this point we added support for &amp;#8220;triple&amp;#8221; to the example and showed how the fully-qualified call to loop (using the modulename:fun() instead of fun() syntax) causes the newest version of the module to be used:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;6&amp;gt; c(ex09).&lt;/span&gt;
&lt;span class='go'&gt;{ok,ex09}&lt;/span&gt;
&lt;span class='go'&gt;7&amp;gt; ex09:client(Pid, triple, 10).&lt;/span&gt;
&lt;span class='go'&gt;wtf&lt;/span&gt;
&lt;span class='go'&gt;8&amp;gt; Pid ! reload.&lt;/span&gt;
&lt;span class='go'&gt;Reloading&lt;/span&gt;
&lt;span class='go'&gt;reload&lt;/span&gt;
&lt;span class='go'&gt;9&amp;gt; ex09:client(Pid, triple, 10).&lt;/span&gt;
&lt;span class='go'&gt;30&lt;/span&gt;
&lt;span class='go'&gt;10&amp;gt; Pid ! stats.&lt;/span&gt;
&lt;span class='go'&gt;Ops: 2, Wtfs: 2&lt;/span&gt;
&lt;span class='go'&gt;stats&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can see from the stats at the end that the global state was kept - the server process staying running during the code upgrade.&lt;/p&gt;
&lt;h2&gt;Download&lt;/h2&gt;
&lt;p&gt;The slides, example code and basic mochiweb comet project we saw last night can be downloaded &lt;a href='http://www.metabrew.com/misc/erlang-hackspace-talk.tar.gz' title='Slides and example code'&gt;here&lt;/a&gt;. I should warn you that unless you saw my talk and the various explanations and disclaimers that went along with the code, it&amp;#8217;s probably not a good place to start or learn from. Have a look at &lt;a href='http://www.learnyousomeerlang.com/' target='_blank'&gt;www.learnyousomeerlang.com&lt;/a&gt; or get one of the two excellent Erlang books. &lt;h2&gt;London Hackspace&lt;/h2&gt; If you live in London you should know about this. &lt;a href='http://russ.garrett.co.uk/' target='_blank'&gt;Russ&lt;/a&gt; and &lt;a href='http://jonty.co.uk/' target='_blank'&gt;Jonty&lt;/a&gt; (who I worked with at Last.fm for years) started &lt;a href='http://london.hackspace.org.uk/' target='_blank'&gt;London Hackspace&lt;/a&gt;: &amp;#8221;&lt;strong&gt;We run a dedicated space for people to learn and build things in London.&quot;&lt;/strong&gt; There are workshops at hackspace meetups on topics ranging from Arduino and electronics hacking, to iPhone development, to Erlang and beyond. Their unofficial slogan could be &amp;#8220;Beer &amp;#38; Hacking&amp;#8221; - it&amp;#8217;s a great place to meet people doing interesting things in London, and to learn new things.&lt;/p&gt;
&lt;a href='http://london.hackspace.org.uk/' target='_blank'&gt;http://london.hackspace.org.uk/&lt;/a&gt;&lt;h2&gt;Playdar&lt;/h2&gt;&lt;a href='http://www.playdar.org/' target='_blank'&gt;Playdar&lt;/a&gt;&lt;a href='http://www.playdar.org/' target='_blank'&gt;http://www.playdar.org/&lt;/a&gt;</description>
    </item>
    
    <item>
      <title>Anti-RDBMS: A list of distributed key-value stores</title>
      <link>http://www.metabrew.com/article/anti-rdbms-a-list-of-distributed-key-value-stores</link>
      <pubDate>Mon, 19 Jan 2009 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/anti-rdbms-a-list-of-distributed-key-value-stores</guid>
      <description>&lt;p&gt;&lt;em&gt;Please Note: this was written January 2009 - see the comments for updates and additional information. A lot has changed since I wrote this.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Perhaps you&amp;#8217;re considering using a dedicated key-value or document store instead of a traditional relational database. Reasons for this might include: &lt;ol&gt;
	&lt;li&gt;You're suffering from Cloud-computing Mania.&lt;/li&gt;
	&lt;li&gt;You need an excuse to 'get your Erlang on'&lt;/li&gt;
	&lt;li&gt;You heard CouchDB was cool.&lt;/li&gt;
	&lt;li&gt;You hate MySQL, and although PostgreSQL is much better, it still doesn't have decent replication. There's no chance you're buying Oracle licenses.&lt;/li&gt;
	&lt;li&gt;Your data is stored and retrieved mainly by primary key, without complex joins.&lt;/li&gt;
	&lt;li&gt;You have a non-trivial amount of data, and the thought of managing lots of RDBMS shards and replication failure scenarios gives you the fear.&lt;/li&gt;
&lt;/ol&gt; Whatever your reasons, there are a lot of options to chose from. At Last.fm we do a lot of batch computation in Hadoop, then dump it out to other machines where it&amp;#8217;s indexed and served up over HTTP and &lt;a href='http://developers.facebook.com/thrift/'&gt;Thrift&lt;/a&gt; as an internal service (stuff like &amp;#8216;most popular songs in London, UK this week&amp;#8217; etc). Presently we&amp;#8217;re using a home-grown index format which points into large files containing lots of data spanning many keys, similar to the Haystack approach mentioned in &lt;a href='http://perspectives.mvdirona.com/2008/06/30/FacebookNeedleInAHaystackEfficientStorageOfBillionsOfPhotos.aspx'&gt;this article about Facebook photo storage&lt;/a&gt;. It works, but rather than build our own replication and partitioning system on top of this, we are looking to potentially replace it with a distributed, resilient key-value store for reasons 4, 5 and 6 above.&lt;/p&gt;

&lt;p&gt;This article represents my notes and research to date on distributed key-value stores (and some other stuff) that might be suitable as RDBMS replacements under the right conditions. I&amp;#8217;m expecting to try some of these out and investigate further in the coming months.&lt;/p&gt;

&lt;h4 id='glossary_and_background_reading'&gt;Glossary and Background Reading&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
 &lt;a href='http://en.wikipedia.org/wiki/Distributed_hash_table'&gt;Distributed Hash Table (DHT)&lt;/a&gt; 
 and algorithms such as Chord or Kadmelia
&lt;/li&gt;

&lt;li&gt;
  &lt;a href='http://www.allthingsdistributed.com/2007/10/amazons_dynamo.html'&gt;Amazon's Dynamo Paper&lt;/a&gt;, 
  and &lt;a href='http://www.readwriteweb.com/archives/amazon_dynamo.php'&gt;this ReadWriteWeb article about Dynamo&lt;/a&gt; which explains why such a system is invaluable
&lt;/li&gt;

&lt;li&gt;
    &lt;a href='http://aws.amazon.com/simpledb/'&gt;Amazon's SimpleDB Service&lt;/a&gt;, 
    and &lt;a href='http://gigaom.com/2007/12/14/amazon-simple-db/'&gt;some&lt;/a&gt;
    &lt;a href='http://www.satine.org/archives/2007/12/13/amazon-simpledb/'&gt;commentary&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href='http://labs.google.com/papers/bigtable.html'&gt;Google's BigTable paper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href='http://en.wikipedia.org/wiki/Paxos_algorithm'&gt;The Paxos Algorithm&lt;/a&gt; - read this page in order to appreciate that knocking up a Paxos implementation isn't something you'd want to do whilst hungover on a Saturday morning.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id='the_shortlist'&gt;The Shortlist&lt;/h3&gt;

&lt;p&gt;Here is a list of projects that could potentially replace a group of relational database shards. Some of these are much more than key-value stores, and aren&amp;#8217;t suitable for low-latency data serving, but are interesting none-the-less.&lt;/p&gt;
&lt;style type='text/css'&gt;
#matrix td{ font-size:90%; vertical-align:top; padding: 3px; } #matrix tr { background: #f0f0f0; } #matrix tr.odd { background: #ddd; } 
#matrix td.bigger {font-size:100%;}
&lt;/style&gt;&lt;table border='0' id='matrix'&gt;
&lt;tbody&gt;
&lt;tr class='odd' style='font-weight:bold;'&gt;
&lt;td class='bigger'&gt;Name&lt;/td&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Fault-tolerance&lt;/td&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;Client Protocol&lt;/td&gt;
&lt;td&gt;Data model&lt;/td&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://project-voldemort.com/' title='http://project-voldemort.com/'&gt;Project Voldemort&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;partitioned, replicated, read-repair&lt;/td&gt;
&lt;td&gt;Pluggable: BerkleyDB, Mysql&lt;/td&gt;
&lt;td&gt;Java API&lt;/td&gt;
&lt;td&gt;Structured / blob / text&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Linkedin, no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://github.com/tuulos/ringo/tree/master' title='http://github.com/tuulos/ringo/tree/master'&gt;Ringo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;partitioned, replicated, immutable&lt;/td&gt;
&lt;td&gt;Custom on-disk (append only log)&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;blob&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;Nokia, no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://code.google.com/p/scalaris/' title='http://code.google.com/p/scalaris/'&gt;Scalaris&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;partitioned, replicated, paxos&lt;/td&gt;
&lt;td&gt;In-memory only&lt;/td&gt;
&lt;td&gt;Erlang, Java, HTTP&lt;/td&gt;
&lt;td&gt;blob&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;OnScale, no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://kai.wiki.sourceforge.net/' title='http://kai.wiki.sourceforge.net/'&gt;Kai&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;partitioned, replicated?&lt;/td&gt;
&lt;td&gt;On-disk Dets file&lt;/td&gt;
&lt;td&gt;Memcached&lt;/td&gt;
&lt;td&gt;blob&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://github.com/cliffmoon/dynomite/tree/master' title='http://github.com/cliffmoon/dynomite/tree/master'&gt;Dynomite&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;partitioned, replicated&lt;/td&gt;
&lt;td&gt;Pluggable: couch, dets&lt;/td&gt;
&lt;td&gt;Custom ascii, Thrift&lt;/td&gt;
&lt;td&gt;blob&lt;/td&gt;
&lt;td&gt;D+&lt;/td&gt;
&lt;td&gt;Powerset, no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://memcachedb.org/' title='http://memcachedb.org/'&gt;MemcacheDB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;replication&lt;/td&gt;
&lt;td&gt;BerkleyDB&lt;/td&gt;
&lt;td&gt;Memcached&lt;/td&gt;
&lt;td&gt;blob&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;some&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://code.google.com/p/thrudb/' title='http://code.google.com/p/thrudb/'&gt;ThruDB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;Replication&lt;/td&gt;
&lt;td&gt;Pluggable: BerkleyDB, Custom, Mysql, S3&lt;/td&gt;
&lt;td&gt;Thrift&lt;/td&gt;
&lt;td&gt;Document oriented&lt;/td&gt;
&lt;td&gt;C+&lt;/td&gt;
&lt;td&gt;Third rail, unsure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://couchdb.apache.org/' title='http://couchdb.apache.org/'&gt;CouchDB&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;Replication, partitioning?&lt;/td&gt;
&lt;td&gt;Custom on-disk&lt;/td&gt;
&lt;td&gt;HTTP, json&lt;/td&gt;
&lt;td&gt;Document oriented (json)&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Apache, yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://code.google.com/p/the-cassandra-project/' title='http://code.google.com/p/the-cassandra-project/'&gt;Cassandra&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Replication, partitioning&lt;/td&gt;
&lt;td&gt;Custom on-disk&lt;/td&gt;
&lt;td&gt;Thrift&lt;/td&gt;
&lt;td&gt;Bigtable meets Dynamo&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;Facebook, no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class='odd'&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://hadoop.apache.org/hbase/' title='http://hadoop.apache.org/hbase/'&gt;HBase&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Replication, partitioning&lt;/td&gt;
&lt;td&gt;Custom on-disk&lt;/td&gt;
&lt;td&gt;Custom API, Thrift, Rest&lt;/td&gt;
&lt;td&gt;Bigtable&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Apache, yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class='bigger' style='font-weight:bold'&gt;&lt;a class='external text' href='http://hypertable.org/' title='http://hypertable.org/'&gt;Hypertable&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;Replication, partitioning&lt;/td&gt;
&lt;td&gt;Custom on-disk&lt;/td&gt;
&lt;td&gt;Thrift, other&lt;/td&gt;
&lt;td&gt;Bigtable&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Zvents, Baidu, yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;
&lt;h3 id='why_5_of_these_arent_suitable'&gt;Why 5 of these aren&amp;#8217;t suitable&lt;/h3&gt;

&lt;p&gt;What I&amp;#8217;m really looking for is a low latency, replicated, distributed key-value store. Something that scales well as you feed it more machines, and doesn&amp;#8217;t require much setup or maintenance - it should just work. The API should be that of a simple hashtable: set(key, val), get(key), delete(key). This would dispense with the hassle of managing a sharded / replicated database setup, and hopefully be capable of serving up data by primary key efficiently.&lt;/p&gt;

&lt;p&gt;Five of the projects on the list are far from being simple key-value stores, and as such don&amp;#8217;t meet the requirements - but they are definitely worth a mention.&lt;/p&gt;

&lt;h4 id='hadoop'&gt;Hadoop&lt;/h4&gt;

&lt;p&gt;We&amp;#8217;re already heavy users of Hadoop, and have been experimenting with Hbase for a while. It&amp;#8217;s much more than a KV store, but latency is too great to serve data to the website. We will probably use Hbase internally for other stuff though - we already have stacks of data in HDFS.&lt;/p&gt;

&lt;h4 id='hypertable'&gt;Hypertable&lt;/h4&gt;

&lt;p&gt;Hypertable provides a similar feature set to Hbase (both are inspired by Google&amp;#8217;s Bigtable). They recently announced a new sponsor, Baidu - the biggest Chinese search engine. Definitely one to watch, but doesn&amp;#8217;t fit the low-latency KV store bill either.&lt;/p&gt;

&lt;h4 id='cassandra'&gt;Cassandra&lt;/h4&gt;

&lt;p&gt;Cassandra sounded very promising when the source was released by Facebook last year. They use it for inbox search. It&amp;#8217;s Bigtable-esque, but uses a DHT so doesn&amp;#8217;t need a central server (one of the Cassandra developers previously worked at Amazon on Dynamo). Unfortunately it&amp;#8217;s languished in relative obscurity since release, because Facebook never really seemed interested in it as an open-source project. From what I can tell there isn&amp;#8217;t much in the way of documentation or a community around the project at present.&lt;/p&gt;

&lt;h4 id='couchdb'&gt;CouchDB&lt;/h4&gt;

&lt;p&gt;CouchDB is an interesting one - it&amp;#8217;s a &amp;#8220;distributed, fault-tolerant and schema-free document-oriented database accessible via a RESTful HTTP/JSON API&amp;#8221;. Data is stored in &amp;#8216;documents&amp;#8217;, which are essentially key-value maps themselves, using the data types you see in JSON. Read the &lt;a href='http://couchdb.apache.org/docs/overview.html'&gt;CouchDB Technical Overview&lt;/a&gt; if you are curious how the web&amp;#8217;s trendiest document database works under the hood. This article on the &lt;a href='http://push.cx/2009/rules-of-database-app-aging'&gt;Rules of Database App Aging&lt;/a&gt; goes some way to explaining why document-oriented databases make sense. CouchDB can do full text indexing of your documents, and lets you express views over your data in Javascript. I could imagine using CouchDB to store lots of data on users: name, age, sex, address, IM name and lots of other fields, many of which could be null, and each site update adds or changes the available fields. In situations like that it quickly gets unwieldly adding and changing columns in a database, and updating versions of your application code to match. Although many people are using CouchDB in production, their FAQ points out they may still make backwards-incompatible changes to the storage format and API before version 1.0.&lt;/p&gt;

&lt;h4 id='thrudb'&gt;ThruDB&lt;/h4&gt;

&lt;p&gt;ThruDB is a document storage and indexing system made up for four components: a document storage service, indexing service, message queue and proxy. It uses Thrift for communication, and has a pluggable storage subsystem, including an Amazon S3 option. It&amp;#8217;s designed to scale well horizontally, and might be a better option that CouchDB if you are running on EC2. I&amp;#8217;ve heard a lot more about CouchDB than Thrudb recently, but it&amp;#8217;s definitely worth a look if you need a document database. It&amp;#8217;s not suitable for our needs for the same reasons as CouchDB.&lt;/p&gt;

&lt;h3 id='distributed_keyvalue_stores'&gt;Distributed key-value stores&lt;/h3&gt;

&lt;p&gt;The rest are much closer to being &amp;#8216;simple&amp;#8217; key-value stores with low enough latency to be used for serving data used to build dynamic pages. Latency will be dependent on the environment, and whether or not the dataset fits in memory. If it does, I&amp;#8217;d expect sub-10ms response time, and if not, it all depends on how much money you spent on spinning rust.&lt;/p&gt;

&lt;h4 id='memcachedb'&gt;MemcacheDB&lt;/h4&gt;

&lt;p&gt;Essentially just memcached that saves stuff to disk using a Berkeley database. As useful as this may be for some situations, it doesn&amp;#8217;t deal with replication and partitioning (sharding), so it would still require a lot of work to make it scale horizontally and be tolerant of machine failure. Other memcached derivatives such as &lt;a href='http://repcached.lab.klab.org/'&gt;repcached&lt;/a&gt; go some way to addressing this by giving you the ability to replicate entire memcache servers (async master-slave setup), but without partitioning it&amp;#8217;s still going to be a pain to manage.&lt;/p&gt;

&lt;h4 id='project_voldemort'&gt;Project Voldemort&lt;/h4&gt;

&lt;p&gt;Looks &lt;em&gt;awesome&lt;/em&gt;. Go and read the &lt;a href='http://project-voldemort.com/'&gt;rather splendid website&lt;/a&gt;, which explains how it works, and includes pretty diagrams and a good description of how consistent hashing is used in the Design section. (If consistent hashing butters your muffin, check out &lt;a href='http://www.last.fm/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients'&gt;libketama - a consistent hashing library&lt;/a&gt; and the &lt;a href='http://www.metabrew.com/article/erlang-libketama-driver-consistent-hashing/'&gt;Erlang libketama driver&lt;/a&gt;). Project-Voldemort handles replication and partitioning of data, and appears to be well written and designed. It&amp;#8217;s reassuring to read in the docs how easy it is to swap out and mock different components for testing. It&amp;#8217;s non-trivial to add nodes to a running cluster, but according to the mailing-list this is being worked on. It sounds like this would fit the bill if we ran it with a Java load-balancer service (see their Physical Architecture Options diagram) that exposed a Thrift API so all our non-Java clients could use it.&lt;/p&gt;

&lt;h4 id='scalaris'&gt;Scalaris&lt;/h4&gt;

&lt;p&gt;Probably the most face-meltingly awesome thing you could build in Erlang. CouchDB, Ejabberd and RabbitMQ are cool, but Scalaris packs by far the most impressive collection of sexy technologies. Scalaris is a key-value store - it uses a modified version of the Chord algorithm to form a DHT, and stores the keys in lexicographical order, so range queries are possible. Although I didn&amp;#8217;t see this explicitly mentioned, this should open up all sorts of interesting options for batch processing - map-reduce for example. On top of the DHT they use an improved version of &lt;a href='http://en.wikipedia.org/wiki/Paxos_algorithm'&gt;Paxos&lt;/a&gt; to guarantee ACID properties when dealing with multiple concurrent transactions. So it&amp;#8217;s a key-value store, but it can guarantee the ACID properties and do proper distributed transactions over multiple keys.&lt;/p&gt;

&lt;p&gt;Oh, and to demonstrate how you can scale a webservice based on such a system, the Scalaris folk implemented their own version of Wikipedia on Scalaris, loaded in the Wikipedia data, and benchmarked their setup to prove it can do more transactions/sec on equal hardware than the classic PHP/MySQL combo that Wikipedia use. Yikes.&lt;/p&gt;

&lt;p&gt;From what I can tell, Scalaris is only memory-resident at the moment and doesn&amp;#8217;t persist data to disk. This makes it entirely impractical to actually run a service like Wikipedia on Scalaris for real - but it sounds like they tackled the hard problems first, and persisting to disk should be a walk in the park after you rolled your own version of Chord and made Paxos your bitch. Take a look at this presentation about Scalaris from the Erlang Exchange conference: &lt;a href='http://video.google.com/videoplay?docid=6981137233069932108&amp;amp;ei=caB0SaPUNIW0iALk-9CMBQ'&gt;
    Scalaris presentation video
  &lt;/a&gt;&lt;/p&gt;

&lt;h4 id='the_rest'&gt;The Rest&lt;/h4&gt;

&lt;p&gt;The reminaing projects, &lt;em&gt;Dynomite&lt;/em&gt;, &lt;em&gt;Ringo&lt;/em&gt; and &lt;em&gt;Ka&lt;/em&gt; are all, more or less, trying to be Dynamo. Of the three, &lt;em&gt;Ringo&lt;/em&gt; looks to be the most specialist - it makes a distinction between small (less than 4KB) and medium-size data items (&amp;lt;100MB). Medium sized items are stored in individual files, whereas small items are all stored in an append-log, the index of which is read into memory at startup. From what I can tell, Ringo can be used in conjunction with the Erlang map-reduce framework Nokia are working on called &lt;a href='http://discoproject.org'&gt;Disco&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I didn&amp;#8217;t find out much about &lt;em&gt;Kai&lt;/em&gt; other than it&amp;#8217;s rather new, and some mentions in Japanese. You can chose either Erlang ets or dets as the storage system (memory or disk, respectively), and it uses the memcached protocol, so it will already have client libraries in many languages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dynomite&lt;/em&gt; doesn&amp;#8217;t have great documentation, but it seems to be more capable than Kai, and is under active development. It has pluggable backends including the storage mechanism from CouchDB, so the 2GB file limit in dets won&amp;#8217;t be an issue. Also I heard that Powerset are using it, so that&amp;#8217;s encouraging.&lt;/p&gt;

&lt;h3 id='summary'&gt;Summary&lt;/h3&gt;

&lt;p&gt;Scalaris is fascinating, and I hope I can find the time to experiment more with it, but it needs to save stuff to disk before it&amp;#8217;d be useful for the kind of things we might use it for at Last.fm.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m keeping an eye on Dynomite - hopefully more information will surface about what Powerset are doing with it, and how it performs at a large scale.&lt;/p&gt;

&lt;p&gt;Based on my research so far, Project-Voldemort looks like the most suitable for our needs. I&amp;#8217;d love to hear more about how it&amp;#8217;s used at LinkedIn, and how many nodes they are running it on.&lt;/p&gt;

&lt;h3 id='what_else_is_there'&gt;What else is there?&lt;/h3&gt;

&lt;p&gt;Here are some other related projects: &lt;ul&gt;
	&lt;li&gt;&lt;a href='http://www.hazelcast.com/'&gt;Hazelcast&lt;/a&gt; - Java DHT/clustering library&lt;/li&gt;
	&lt;li&gt;&lt;a href='http://blitiri.com.ar/p/nmdb/'&gt;nmdb&lt;/a&gt; - a network database (dbm-style)&lt;/li&gt;
	&lt;li&gt;&lt;a href='http://open-chord.sourceforge.net/'&gt;Open Chord&lt;/a&gt; - Java DHT&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;If you know of anything I&amp;#8217;ve missed off the list, or have any feedback/suggestions, please post a comment. I&amp;#8217;m especially interested in hearing about people who&amp;#8217;ve tested or are using KV-stores in lieu of relational databases.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UPDATE 1:&lt;/em&gt; Corrected table: memcachedb does replication, as per BerkeleyDB.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>How we use IRC at Last.fm</title>
      <link>http://www.metabrew.com/article/how-we-use-irc-at-lastfm</link>
      <pubDate>Thu, 08 Jan 2009 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/how-we-use-irc-at-lastfm</guid>
      <description>&lt;p&gt;Everyone that works at Last.fm is typically connected to our IRC server. We have different channels per team, as well as a company-wide channel, and a few channels dedicated to automated monitoring.&lt;/p&gt;

&lt;p&gt;Sometimes it makes much more sense to discuss / ask questions on IRC instead of email, and it&amp;#8217;s useful to be able to raise people who are not in the office. That said, the main reason I&amp;#8217;m writing this post is to mention the dev-support bot we use: irccat.&lt;/p&gt;

&lt;h3 id='irccat__development_support_bot'&gt;IRCCat - Development support bot&lt;/h3&gt;

&lt;p&gt;The irccat bot joins all your channels, and waits for messages on a specified ip:port on your internal network. Anything you send to that port will be sent to IRC by the bot. IRCCat - as in, &lt;code&gt;cat&lt;/code&gt; to IRC.&lt;/p&gt;

&lt;p&gt;Using netcat, you can easily send events to irc from shell scripts:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Something just happened&amp;quot;&lt;/span&gt; | nc -q0 somemachine 12345
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That will send to the default channel only (first in the config file). You can direct messages to specific combinations of channels (#) or users (@) like so:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;#syschan Starting backup job&amp;quot;&lt;/span&gt; | nc -q0 somemachine 12345
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='nb'&gt;echo&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;#musicteam,#legal,@alice New album uploaded: ...&amp;quot;&lt;/span&gt; | nc -q0 somemachine 12345
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Some of the things we automatically send to appropriate IRC channels: &lt;ul&gt;
	&lt;li&gt;SVN commits&lt;/li&gt;
	&lt;li&gt;JIRA issue tracker updates&lt;/li&gt;
	&lt;li&gt;Nagios alerts for monitored hosts and services&lt;/li&gt;
	&lt;li&gt;Deployment notices to testing/staging/production&lt;/li&gt;
	&lt;li&gt;Results of automated tests if something bad happens&lt;/li&gt;
	&lt;li&gt;Links to pics from security camfeed when someone opens the office door out of hours&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;We also post messages from automated backup jobs etc, which helps correlate such events with any unusual load spikes or glitches in usually-smooth graphs.&lt;/p&gt;

&lt;p&gt;In addition to providing a cat-to-irc conduit, irccat will also hand off commands to a script you can provide. We use this to expose lookup tools and some admin functions to our support staff and developers. The handler script we use is PHP, and has access to our core website libs. Typing &amp;#8220;?pokereleasenode&amp;#8221;, &amp;#8220;?lookup user RJ&amp;#8221; or &amp;#8220;?uncache artist Radiohead&amp;#8221; is faster than writing a throw-away script, more accessible to non-developers, less hassle than a web interface and creates a public log so people can see what&amp;#8217;s going on.&lt;/p&gt;

&lt;p&gt;The bot is written in Java, it&amp;#8217;s easy to build and configure, all the deps are included:&lt;/p&gt;
&lt;a href='http://github.com/RJ/irccat/tree/master' target='_blank' title='IRCcat source on GitHub'&gt;http://github.com/RJ/irccat/tree/master&lt;/a&gt;</description>
    </item>
    
    <item>
      <title>Getting to know ejabberd and writing modules</title>
      <link>http://www.metabrew.com/article/getting-to-know-ejabberd-and-writing-modules</link>
      <pubDate>Sun, 23 Nov 2008 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/getting-to-know-ejabberd-and-writing-modules</guid>
      <description>&lt;p&gt;I started poking around in the ejabberd source code to see what I could learn. I couldn&amp;#8217;t find much in the way of high level documentation that talks about how the various bits of ejabberd talk to each other, so I&amp;#8217;m starting to piece it together myself.&lt;/p&gt;

&lt;p&gt;After compiling ejabberd I made a php script I could use with the external authentication system. Here&amp;#8217;s a version that supports just two hardcoded users:&lt;/p&gt;

&lt;p&gt;ejabberd.cfg:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;auth_method&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;external&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;extauth_program&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/tmp/auth.php&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;auth.php:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='php'&gt;&lt;span class='x'&gt;#!/usr/bin/php&lt;/span&gt;
&lt;span class='cp'&gt;&amp;lt;?&lt;/span&gt;
&lt;span class='nv'&gt;$fh&lt;/span&gt;  &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;fopen&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;php://stdin&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;!&lt;/span&gt;&lt;span class='nv'&gt;$fh&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
    &lt;span class='k'&gt;die&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;Cannot open STDIN&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='nv'&gt;$users&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;array&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;user1&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;=&amp;gt;&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;password1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;user2&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;=&amp;gt;&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;password2&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

&lt;span class='k'&gt;do&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='nv'&gt;$lenBytes&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;fgets&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$fh&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nv'&gt;$len&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;unpack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;$lenBytes&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nv'&gt;$len&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;$len&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$len&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;continue&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='nv'&gt;$msg&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;fgets&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$fh&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;$len&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nv'&gt;$toks&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nb'&gt;explode&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;$msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='nv'&gt;$method&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;array_shift&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$toks&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='k'&gt;switch&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$method&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
        &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;auth&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;
            &lt;span class='k'&gt;list&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$username&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;$server&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;$password&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;$toks&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
            &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;@&lt;/span&gt;&lt;span class='nv'&gt;$users&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;$username&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='nv'&gt;$password&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
                &lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='nb'&gt;pack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='c1'&gt;// ok&lt;/span&gt;
            &lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='k'&gt;else&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='nb'&gt;pack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='c1'&gt;// fail&lt;/span&gt;
            &lt;span class='p'&gt;}&lt;/span&gt;
            &lt;span class='k'&gt;break&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

        &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;isuser&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;
            &lt;span class='k'&gt;list&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$username&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;$server&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;$toks&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
            &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;isset&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;$users&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;$username&lt;/span&gt;&lt;span class='p'&gt;])){&lt;/span&gt;
                &lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='nb'&gt;pack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='c1'&gt;// yes&lt;/span&gt;
            &lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='k'&gt;else&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='nb'&gt;pack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='c1'&gt;// nope&lt;/span&gt;
            &lt;span class='p'&gt;}&lt;/span&gt;
            &lt;span class='k'&gt;break&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

        &lt;span class='k'&gt;default&lt;/span&gt;&lt;span class='o'&gt;:&lt;/span&gt;
            &lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='nb'&gt;pack&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;nn&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;&lt;span class='c1'&gt;// fail&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='k'&gt;while&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;true&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I stripped down the ejabberd config to just load what I considered the bare essentials. Here is the modules section I&amp;#8217;m testing with:&lt;/p&gt;

&lt;p&gt;From ejabberd.cfg:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;modules&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
 &lt;span class='p'&gt;[&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_caps&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;     &lt;span class='p'&gt;[]},&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_disco&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;    &lt;span class='p'&gt;[]},&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_roster&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;   &lt;span class='p'&gt;[]},&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_pubsub&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;   &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='c'&gt;% requires mod_caps&lt;/span&gt;
                  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;access_createnode&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;pubsub_createnode&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;plugins&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;pep&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;]}&lt;/span&gt;
                 &lt;span class='p'&gt;]},&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_mnesiaweb&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;     &lt;span class='p'&gt;[]},&lt;/span&gt;
  &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;mod_thriftctl&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;     &lt;span class='p'&gt;[]}&lt;/span&gt;
 &lt;span class='p'&gt;]}.&lt;/span&gt;
 
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;mod_disco&lt;/em&gt; deals with discovery, so clients can find out what the server supports. mod_roster deals with rosters (buddy lists etc) using mnesia. mod_pubsub is enabled because I want to use &lt;a href='http://xmpp.org/extensions/xep-0118.html'&gt;User Tune&lt;/a&gt;, an extension that lets you broadcast the name of the song you are playing to all everyone in your roster. mod_caps provides &lt;a href='http://xmpp.org/extensions/xep-0115.html'&gt;XEP-115&lt;/a&gt; - an extension for broadcasting and dynamically discovering client, device, or generic entity capabilities. mod_caps is a requirement of mod_pubsub.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve removed the module that allows users to register, although I made a few accounts first whilst testing. The last two modules, mod_mnesiaweb and mod_thriftctl are modules I wrote.&lt;/p&gt;

&lt;h2 id='mod_mnesiaweb'&gt;mod_mnesiaweb&lt;/h2&gt;

&lt;p&gt;To help figure out what&amp;#8217;s going on inside of ejabberd, it&amp;#8217;s useful to be able to easily browse the mnesia database. &lt;a href='http://yaws.hyber.org/'&gt;Yaws&lt;/a&gt; comes with an appmod that does this, called ymnesia. This ejabberd module will start yaws in embedded mode and run this appmod, enabling you to explore the mnesia database from a web browser.&lt;/p&gt;
&lt;i&gt;&lt;b&gt;Yaws observation:&lt;/b&gt; yaws didn't appear to build ymnesia by default, I edited the Makefile in src and added &quot;ymnesia&quot; to the module list. Also, if ./configure fails, the package you are probably missing is libpam0g-dev&lt;/i&gt;
&lt;p&gt;mod_mnesiaweb:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='c'&gt;% Ejabberd module that runs yaws in embedded mode,&lt;/span&gt;
&lt;span class='c'&gt;% and loads the ymnesia appmod for browsing mnesia.&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;mod_mnesiaweb&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;author&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;rj@last.fm&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/usr/local/lib/yaws/include/yaws.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;behaviour&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;gen_mod&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Host&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Opts&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;Port&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;gen_mod&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;get_opt&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;port&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Opts&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;8001&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nn'&gt;code&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;add_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/usr/local/lib/yaws/ebin&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nn'&gt;application&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;set_env&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;yaws&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;embedded&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;true&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nn'&gt;application&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;yaws&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;GC&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;yaws_config&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_default_gconf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;false&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;yawstest&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;SC&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nl'&gt;#sconf&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;port&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Port&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='n'&gt;servername&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;ejabnesia&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='n'&gt;listen&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
        &lt;span class='n'&gt;appmods&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;showdb&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ymnesia&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
        &lt;span class='n'&gt;docroot&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;wwwroot&amp;quot;&lt;/span&gt;
        &lt;span class='p'&gt;},&lt;/span&gt;
    &lt;span class='nn'&gt;yaws_api&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;setconf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;GC&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[[&lt;/span&gt;&lt;span class='nv'&gt;SC&lt;/span&gt;&lt;span class='p'&gt;]]),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Host&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;application&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;yaws&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To compile it:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;erlc -pa ${EJAB_SRC} -I ${EJAB_SRC} mod_mnesiaweb.erl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;where EJAB_SRC is the ejabberd-2.X.X/src directory, after you&amp;#8217;ve compiled from source (so the beams are there too).&lt;/p&gt;

&lt;p&gt;Copy the resulting mod_mnesiaweb.beam to /var/lib/ejabberd/ebin so ejabberd finds it, and it should work. Hit up http://localhost:8001/showdb/ in your browser and you can explore the mnesia database.&lt;/p&gt;

&lt;p&gt;Use the match syntax to filter tables. For example to find everyone in my roster, I use this in the input box next to roster:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;roster&lt;/span&gt;&lt;span class='p'&gt;,{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;RJ&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,[]}},&lt;/span&gt; &lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Not pretty, but it gets the job done. You can just view the entire table, copy a record then replace fields with &amp;#8216;_&amp;#8217; to build queries.&lt;/p&gt;

&lt;h2 id='mod_thriftctl'&gt;mod_thriftctl&lt;/h2&gt;

&lt;p&gt;Next up I wanted to try the Erlang &lt;a href='http://incubator.apache.org/thrift/'&gt;Thrift&lt;/a&gt; bindings (written by the folks at &lt;a href='http://amiest-devblog.blogspot.com/2008/01/alternative-erlang-bindings-for-thrift.html'&gt;Amie St.&lt;/a&gt;), and expose some useful functionality for controlling the server.&lt;/p&gt;

&lt;p&gt;If you aren&amp;#8217;t familiar with Thrift, I recommend reading about it first. In a nutshell, you write your API using an IDL (a .thrift file) and the thrift compiler creates client libraries, and server code in various different languages. It&amp;#8217;s an RPC mechanism, and useful in a mixed environment.&lt;/p&gt;

&lt;p&gt;mod_thriftctl.thrift:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='php'&gt;&lt;span class='x'&gt;#!/usr/local/bin/thrift -php -erl&lt;/span&gt;

&lt;span class='x'&gt;struct JabberUser {&lt;/span&gt;
&lt;span class='x'&gt;    1: string name,&lt;/span&gt;
&lt;span class='x'&gt;    2: string server&lt;/span&gt;
&lt;span class='x'&gt;}&lt;/span&gt;

&lt;span class='x'&gt;service Ejabthrift {&lt;/span&gt;
&lt;span class='x'&gt;    /* add ruser to roster of luser, and visa-versa. also routes presence to users if online  */&lt;/span&gt;
&lt;span class='x'&gt;    void add_friend(        1: JabberUser luser,&lt;/span&gt;
&lt;span class='x'&gt;                            2: JabberUser ruser&lt;/span&gt;
&lt;span class='x'&gt;                            ),&lt;/span&gt;

&lt;span class='x'&gt;    /* remove ruser from luser&amp;#39;s roster */&lt;/span&gt;
&lt;span class='x'&gt;    void remove_friend(    1: JabberUser luser, 2: JabberUser ruser ),&lt;/span&gt;

&lt;span class='x'&gt;    /* make it look like fromuser sent a message to touser */&lt;/span&gt;
&lt;span class='x'&gt;    void spoof_message( 1: JabberUser fromuser, 2: JabberUser touser, 3: string message, 4: string subject ),&lt;/span&gt;
&lt;span class='x'&gt;    /* .. or a chat message */&lt;/span&gt;
&lt;span class='x'&gt;    void spoof_chat(    1: JabberUser fromuser, 2: JabberUser touser, 3: string message, 4: string thread ),&lt;/span&gt;

&lt;span class='x'&gt;    /* sends PEP usertune message, see http://xmpp.org/extensions/xep-0118.html */&lt;/span&gt;
&lt;span class='x'&gt;    void publish_np ( 1: JabberUser fromuser, 2: string artist, 3: string album, 4: string track, 5: i32 tracklength, 6: i32 tracknum )&lt;/span&gt;
&lt;span class='x'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run that .thrift file, and you get gen-php and gen-erl directories, with php client code, and erlang files needed to build a server.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the ejabberd module, which starts a thrift server:&lt;/p&gt;

&lt;p&gt;mod_thriftctl:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='c'&gt;%&lt;/span&gt;
&lt;span class='c'&gt;% A module to control ejabberd with a thrift interface.&lt;/span&gt;
&lt;span class='c'&gt;%&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;mod_thriftctl&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;author&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;rj@last.fm&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='c'&gt;% ejabberd headers:&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;ejabberd.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_roster.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;jlib.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='c'&gt;% thrift server headers:&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;thrift.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;transport/tSocket.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;protocol/tBinaryProtocol.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;server/tErlServer.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;transport/tErlAcceptor.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='c'&gt;% we are an ejabberd module:&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;behaviour&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;gen_mod&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='c'&gt;% our thrift service:&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;ejabthrift_thrift.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_thriftctl_types.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;   &lt;span class='n'&gt;add_friend&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;remove_friend&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
            &lt;span class='n'&gt;spoof_message&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;spoof_chat&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
            &lt;span class='n'&gt;publish_np&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;6&lt;/span&gt;
        &lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='c'&gt;% convert thrift Jabberuser into ejabberd jid&lt;/span&gt;
&lt;span class='nf'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_record&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;jabberUser&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nl'&gt;#jid&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='n'&gt;user&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='nl'&gt;#jabberUser.name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;server&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='nl'&gt;#jabberUser.server&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;resource&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
          &lt;span class='n'&gt;luser&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='nl'&gt;#jabberUser.name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;lserver&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;Jabberuser&lt;/span&gt;&lt;span class='nl'&gt;#jabberUser.server&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;lresource&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;spoof_message&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ToU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subject&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;F&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;T&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ToU&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;XmlBody&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
               &lt;span class='p'&gt;[&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;)},&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;T&lt;/span&gt;&lt;span class='p'&gt;)}&lt;/span&gt;
               &lt;span class='p'&gt;],&lt;/span&gt;
               &lt;span class='p'&gt;[&lt;/span&gt;
               &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;subject&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subject&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
               &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;}]}&lt;/span&gt;
               &lt;span class='p'&gt;]&lt;/span&gt;
              &lt;span class='p'&gt;},&lt;/span&gt;
    &lt;span class='nn'&gt;ejabberd_router&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;route&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;T&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;XmlBody&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;spoof_chat&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ToU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Thread&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;F&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;T&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ToU&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;XmlBody&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;message&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
               &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;chat&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;)},&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;T&lt;/span&gt;&lt;span class='p'&gt;)}&lt;/span&gt;
               &lt;span class='p'&gt;],&lt;/span&gt;
               &lt;span class='p'&gt;[&lt;/span&gt;
               &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;thread&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Thread&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
               &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;body&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;}]}&lt;/span&gt;
               &lt;span class='p'&gt;]&lt;/span&gt;
              &lt;span class='p'&gt;},&lt;/span&gt;
    &lt;span class='nn'&gt;ejabberd_router&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;route&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;T&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;XmlBody&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;publish_np&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ArtistS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AlbumS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;TrackS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LengthI&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;TrackNumI&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;From&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ju2jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;FromU&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='c'&gt;% The usertune message must contain binaries, not strings or ints&lt;/span&gt;
    &lt;span class='nv'&gt;FromStr&lt;/span&gt;     &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;Artist&lt;/span&gt;      &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ArtistS&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;Album&lt;/span&gt;       &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;AlbumS&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;Track&lt;/span&gt;       &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;TrackS&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;Length&lt;/span&gt;      &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;LengthI&lt;/span&gt;&lt;span class='p'&gt;])),&lt;/span&gt;
    &lt;span class='nv'&gt;TrackNum&lt;/span&gt;    &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;TrackNumI&lt;/span&gt;&lt;span class='p'&gt;])),&lt;/span&gt;
    &lt;span class='nv'&gt;Xml&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;iq&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;from&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;FromStr&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                 &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                 &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;pub1&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
                &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;  &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                 &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;pubsub&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                  &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;xmlns&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;http://jabber.org/protocol/pubsub&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
                  &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;    &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                   &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;publish&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;http://jabber.org/protocol/tune&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
                    &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;      &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;item&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],&lt;/span&gt;
                      &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;        &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                       &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;tune&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                        &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;xmlns&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;http://jabber.org/protocol/tune&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
                        &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;          &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;artist&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],&lt;/span&gt;
                          &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Artist&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;          &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;length&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Length&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;          &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],&lt;/span&gt;
                          &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Album&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;          &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;title&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],&lt;/span&gt;
                          &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Track&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;          &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;track&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[],[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;TrackNum&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;        &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                       &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;      &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;    &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                   &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;  &amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
                 &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
    &lt;span class='c'&gt;% PEP means you act as a pubsub node yourself,&lt;/span&gt;
    &lt;span class='c'&gt;% so it&amp;#39;s addressed to yourself and is broadcast to your friends automatically:&lt;/span&gt;
    &lt;span class='nn'&gt;ejabberd_router&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;route&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Xml&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='c'&gt;% adds bi-directional friend relationship immediately for both users.&lt;/span&gt;
&lt;span class='nf'&gt;add_friend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;     &lt;span class='nl'&gt;#jabberUser&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;server&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                &lt;span class='nl'&gt;#jabberUser&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;server&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;AskMessage&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nv'&gt;Group&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nv'&gt;Subtype&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;both&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='n'&gt;subscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subtype&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AskMessage&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;subscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subtype&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AskMessage&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;route_rosteritem&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subtype&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;route_rosteritem&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subtype&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;remove_friend&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='nl'&gt;#jabberUser&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;server&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='nl'&gt;#jabberUser&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;name&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;server&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='n'&gt;unsubscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;unsubscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;route_rosteritem&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;remove&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;route_rosteritem&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LU&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LS&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;remove&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;unsubscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;Key&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;,&lt;/span&gt;
       &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;}},&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;transaction&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;delete&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;roster&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Key&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;route_rosteritem&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Nick&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subscription&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;LJID&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;RJID&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;make_jid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;ToS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LJID&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;ItemJIDS&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;jlib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;jid_to_string&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;RJID&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;GroupXML&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;group&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='n'&gt;xmlcdata&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;}]},&lt;/span&gt;
    &lt;span class='nv'&gt;Item&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;item&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;jid&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ItemJIDS&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Nick&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
         &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;subscription&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subscription&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
        &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;GroupXML&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='nv'&gt;Query&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;query&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;xmlns&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;?NS_ROSTER&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Item&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='nv'&gt;Packet&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;xmlelement&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;iq&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;to&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ToS&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Query&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt;
    &lt;span class='nn'&gt;ejabberd_router&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;route&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LJID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LJID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Packet&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;


&lt;span class='nf'&gt;subscribe&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Nick&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Xattrs&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;R&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nl'&gt;#roster&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;usj&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;,{&lt;/span&gt;&lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;,[]}},&lt;/span&gt;
                &lt;span class='n'&gt;us&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;LocalUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;LocalServer&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
                &lt;span class='n'&gt;jid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;RemoteUser&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;RemoteServer&lt;/span&gt;&lt;span class='p'&gt;,[]},&lt;/span&gt;
                &lt;span class='n'&gt;name&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Nick&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                &lt;span class='n'&gt;subscription&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='c'&gt;% none, to=you see him, from=he sees you, both&lt;/span&gt;
                &lt;span class='n'&gt;ask&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;none&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='c'&gt;% out=send request, in=somebody requests you, none&lt;/span&gt;
                &lt;span class='n'&gt;groups&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Group&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
                &lt;span class='n'&gt;askmessage&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Xattrs&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='c'&gt;% example: [{&amp;quot;category&amp;quot;,&amp;quot;conference&amp;quot;}]&lt;/span&gt;
                &lt;span class='n'&gt;xs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[]&lt;/span&gt;
               &lt;span class='p'&gt;},&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;transaction&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;R&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Host&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Opts&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='no'&gt;?INFO&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_ejabthrift start().&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt;
    &lt;span class='c'&gt;%% get options&lt;/span&gt;
    &lt;span class='nv'&gt;Port&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;gen_mod&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;get_opt&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;port&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Opts&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;9000&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;

    &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;thrift&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='no'&gt;?INFO&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_ejabthrift thrift:start().&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt;

    &lt;span class='nv'&gt;Handler&lt;/span&gt;   &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nv'&gt;Processor&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ejabthrift_thrift&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='nv'&gt;TF&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;tBufferedTransportFactory&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
    &lt;span class='nv'&gt;PF&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;tBinaryProtocolFactory&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;

    &lt;span class='nv'&gt;ServerTransport&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;tErlAcceptor&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nv'&gt;ServerFlavor&lt;/span&gt;    &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;tErlServer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;

    &lt;span class='nv'&gt;Server&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;oop&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start_new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ServerFlavor&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Port&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Handler&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Processor&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ServerTransport&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;TF&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;PF&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;

    &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='no'&gt;?R0&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Server&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;effectful_serve&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;    &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='no'&gt;?INFO&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_ejabthrift: Thrift server (&lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='s'&gt;) listening on port &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;Host&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Port&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='c'&gt;% put Server into process dictionary (needed for clean stop)&lt;/span&gt;
        &lt;span class='nb'&gt;put&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;thrift_server_reference&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Server&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='nv'&gt;Error&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='no'&gt;?ERROR_MSG&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;mod_ejabthrift: Error starting thrift server: &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Error&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='nv'&gt;Error&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Host&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='no'&gt;?C0&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;get&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;thrift_server_reference&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To build, first build the gen-erl code:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;erlc -pa ${EJAB_SRC} -I ${EJAB_SRC} -I ${ERL_THRIFT}/include -I ./gen-erl -o ./gen-erl ./gen-erl/*.erl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Where ERL_THRIFT is the lib/erl directory from the amiethrift code, git://repo.or.cz/amiethrift.git&lt;/p&gt;

&lt;p&gt;Then compile the module:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;erlc -pa ${EJAB_SRC} -I ${EJAB_SRC} -I ${ERL_THRIFT}/include -I ./gen-erl *.erl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To install, copy all the beam files to the ejabberd ebin dir:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;sudo cp *.beam gen-erl/*.beam /var/lib/ejabberd/ebin/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This is inspired by mod_xmlrpc, which is in ejabberd-modules. As you can see from the start function, that&amp;#8217;s what it takes to start a thrift server. It&amp;#8217;s now trivial to call into ejabberd from other languages. For example, if you started listening to a song using a flash player on the website, a php webservice could make a user tune announcement on your behalf, or spoof messages from you boasting how much you love listening to Paris Hilton.&lt;/p&gt;

&lt;p&gt;If anyone knows where I can read about the ejabberd architecture / design, so I don&amp;#8217;t have to piece it all together myself, please let me know.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>ssh hack: connect directly to machine via a firewall box</title>
      <link>http://www.metabrew.com/article/ssh-hack-connect-directly-to-machine-via-a-firewall-box</link>
      <pubDate>Mon, 17 Nov 2008 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/ssh-hack-connect-directly-to-machine-via-a-firewall-box</guid>
      <description>&lt;p&gt;It&amp;#8217;s common to have to ssh to firewall / gateway machine, then ssh to the machine you want to work on within a server network.&lt;/p&gt;

&lt;p&gt;Typically you&amp;#8217;d do this from your local machine:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; ssh firewall.example.com
&lt;span class='go'&gt;Password:&lt;/span&gt;
&lt;span class='gp'&gt;$&lt;/span&gt; ssh my-private-host
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I finally got bored of doing this, and created the following file: &lt;em&gt;/usr/bin/sssh&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='bash'&gt;&lt;span class='c'&gt;#!/bin/bash&lt;/span&gt;
ssh -oproxycommand&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;ssh -q firewall.example.com nc -q0 %h %p&amp;quot;&lt;/span&gt; &lt;span class='nv'&gt;$*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now I can use the &lt;code&gt;sssh&lt;/code&gt; command to connect to hosts using the firewall machine as a proxy. Like most good hacks, this uses netcat.&lt;/p&gt;

&lt;p&gt;Eg:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; sssh 10.1.2.3 
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Will connect me directly to a machine on the server network, via the firewall box. Seeing as it passes all parameters to ssh (the &lt;code&gt;$*&lt;/code&gt; bit) you can do port forwards and X-forwarding as usual too:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; sssh -L 5432:localhost:5432 my-vm
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This lets me tunnel the port for a PostgreSQL running on my development vm (&lt;code&gt;my-vm&lt;/code&gt;) in a single command. I have all my keys installed, so no passwords needed - I estimate this will save me about 60 seconds every day.&lt;/p&gt;</description>
    </item>
    
    <item>
      <title>A Million-user Comet Application with Mochiweb, Part 3</title>
      <link>http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3</link>
      <pubDate>Tue, 04 Nov 2008 00:00:00 GMT</pubDate>
      <author>rj@metabrew.com (Richard Jones)</author>
      <guid>http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3</guid>
      <description>&lt;p&gt;In &lt;a href='http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1/'&gt;Part 1&lt;/a&gt; and &lt;a href='http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-2/'&gt;Part 2&lt;/a&gt; of this series we built a comet application using mochiweb, and learned how to route messages to connected users. We managed to squeeze application memory down to 8KB per connection. We did ye olde c10k test, and observed what happened with 10,000 connected users. We made graphs. It was fun, but now it&amp;#8217;s time to make good on the claims made in the title, and turn it up to 1 million connections.&lt;/p&gt;

&lt;p&gt;This post covers the following: &lt;ul&gt;
	&lt;li&gt;Add a pubsub-like subscription database using Mnesia&lt;/li&gt;
	&lt;li&gt;Generate a realistic friends dataset for a million users&lt;/li&gt;
	&lt;li&gt;Tune mnesia and bulk load in our friends data&lt;/li&gt;
	&lt;li&gt;Opening a million connections from one machine&lt;/li&gt;
	&lt;li&gt;Benchmark with 1 Million connected users&lt;/li&gt;
	&lt;li&gt;Libevent + C for connection handling&lt;/li&gt;
	&lt;li&gt;Final thoughts&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;One of the challenging parts of this test was actually being able to open 1M connections from a single test machine. Writing a server to accept 1M connections is easier than actually creating 1M connections to test it with, so a fair amount of this article is about the techniques used to open 1M connections from a single machine.&lt;/p&gt;

&lt;h2 id='getting_our_pubsub_on'&gt;Getting our pubsub on&lt;/h2&gt;

&lt;p&gt;In &lt;a href='http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-2/'&gt;Part 2&lt;/a&gt; we used the router to send messages to specific users. This is fine for a chat/IM system, but that there are sexier things we could do instead. Before we launch into a large-scale test, let&amp;#8217;s add one more module - a subscription database. We want the application store who your friends are, so it can push you all events generated by people on your friends list.&lt;/p&gt;

&lt;p&gt;My intention is to use this for Last.fm so I can get a realtime feed of songs &lt;a href='http://www.last.fm/user/RJ/friends'&gt;my friends&lt;/a&gt; are currently listening to. It could equally apply to other events generated on social networks. Flickr photo uploads, Facebook newsfeed items, Twitter messages etc. FriendFeed even have a realtime API in beta, so this kind of thing is definitely topical. (Although I&amp;#8217;ve not heard of anyone except Facebook using Erlang for this kind of thing).&lt;/p&gt;

&lt;h2 id='implementing_the_subscriptionmanager'&gt;Implementing the subscription-manager&lt;/h2&gt;

&lt;p&gt;We&amp;#8217;re implementing a general subscription manager, but we&amp;#8217;ll be subscribing people to everyone on their friends list automatically - so you could also think of this as a friends database for now.&lt;/p&gt;

&lt;p&gt;The subsmanager API: &lt;ul&gt;
	&lt;li&gt;add_subscriptions([{Subscriber, Subscribee},...])&lt;/li&gt;
	&lt;li&gt;remove_subscriptions([{Subscriber, Subscribee},...])&lt;/li&gt;
	&lt;li&gt;get_subscribers(User)&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;subsmanager.erl&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subsmanager&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;behaviour&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;include&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;/usr/local/lib/erlang/lib/stdlib-1.15.4/include/qlc.hrl&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;init&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_call&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_cast&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_info&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;terminate&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;code_change&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;add_subscriptions&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='n'&gt;remove_subscriptions&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='n'&gt;get_subscribers&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='n'&gt;first_run&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
         &lt;span class='n'&gt;start_link&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;record&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nl'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;record&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nl'&gt;state&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{}).&lt;/span&gt; &lt;span class='c'&gt;% state is all in mnesia&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;global&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;whereis_name&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;)).&lt;/span&gt;

&lt;span class='nf'&gt;start_link&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start_link&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;global&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[]).&lt;/span&gt;

&lt;span class='nf'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='nf'&gt;add_subscriptions&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;add_subscriptions&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='n'&gt;infinity&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;remove_subscriptions&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;remove_subscriptions&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='n'&gt;infinity&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;get_subscribers&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;User&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;get_subscribers&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;User&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='c'&gt;%%&lt;/span&gt;

&lt;span class='nf'&gt;init&lt;/span&gt;&lt;span class='p'&gt;([])&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
    &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Waiting on mnesia tables..&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;wait_for_tables&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='mi'&gt;30000&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;Info&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;table_info&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;all&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;OK. Subscription table info: &lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;Info&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nl'&gt;#state&lt;/span&gt;&lt;span class='p'&gt;{}&lt;/span&gt; &lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;add_subscriptions&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='c'&gt;% Transactionally is slower:&lt;/span&gt;
    &lt;span class='c'&gt;% F = fun() -&amp;gt;&lt;/span&gt;
    &lt;span class='c'&gt;%         [ ok = mnesia:write(S) || S &amp;lt;- SubsList ]&lt;/span&gt;
    &lt;span class='c'&gt;%     end,&lt;/span&gt;
    &lt;span class='c'&gt;% mnesia:transaction(F),&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;dirty_write&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;S&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;S&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt; &lt;span class='p'&gt;],&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;remove_subscriptions&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;F&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;delete_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;S&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;S&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;SubsList&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;transaction&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;get_subscribers&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;User&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;F&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nv'&gt;Subs&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;dirty_match_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nl'&gt;#subscription&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;User&lt;/span&gt;&lt;span class='p'&gt;}),&lt;/span&gt;
        &lt;span class='nv'&gt;Users&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Dude&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nl'&gt;#subscription&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;Dude&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='p'&gt;_}&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;Subs&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt;
        &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Users&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;handle_cast&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;span class='nf'&gt;handle_info&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;terminate&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Reason&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;stop&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;code_change&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;OldVersion&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;Extra&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Reloading code for ?MODULE&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[]),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='c'&gt;%%&lt;/span&gt;

&lt;span class='nf'&gt;first_run&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;create_schema&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='nb'&gt;node&lt;/span&gt;&lt;span class='p'&gt;()]),&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
    &lt;span class='nv'&gt;Ret&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;mnesia&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;create_table&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='p'&gt;[&lt;/span&gt;
     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;disc_copies&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nb'&gt;node&lt;/span&gt;&lt;span class='p'&gt;()]},&lt;/span&gt;
     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;record_info&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;fields&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;)},&lt;/span&gt;
     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;index&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='p'&gt;]},&lt;/span&gt; &lt;span class='c'&gt;%index subscribee too&lt;/span&gt;
     &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;type&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;bag&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;]),&lt;/span&gt;
    &lt;span class='nv'&gt;Ret&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Noteworthy points: &lt;ul&gt;
	&lt;li&gt;I've included qlc.hrl, needed for mnesia queries using list comprehension, using an absolute path. That can't be best practice, it wasn't finding it otherwise though.&lt;/li&gt;
	&lt;li&gt;&lt;code&gt;get_subscribers&lt;/code&gt; spawns another process and delegates the job of replying to that process, using &lt;code&gt;gen_server:reply&lt;/code&gt;. This means the gen_server loop won't block on that call if we throw lots of lookups at it and mnesia slows down.&lt;/li&gt;
	&lt;li&gt;&lt;code&gt;rr(”subsmanager.erl”).&lt;/code&gt; in the example below allows you to use record definitions in the erl shell. Putting your record definitions into a &lt;code&gt;records.hrl&lt;/code&gt; file and including that in your modules is considered better style. I inlined it for brevity.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Now to test it. &lt;code&gt;first_run()&lt;/code&gt; creates the mnesia schema, so it&amp;#8217;s important to run that first. Another potential gotcha with mnesia is that (by default) the database can only be accessed by the node that created it, so give the erl shell a name, and stick with it.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; mkdir /var/mnesia
&lt;span class='gp'&gt;$&lt;/span&gt; erl -boot start_sasl -mnesia dir &lt;span class='s1'&gt;&amp;#39;&amp;quot;/var/mnesia_data&amp;quot;&amp;#39;&lt;/span&gt; -sname subsman
&lt;span class='go'&gt;(subsman@localhost)1&amp;gt; c(subsmanager).&lt;/span&gt;
&lt;span class='go'&gt;{ok,subsmanager}&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)2&amp;gt; subsmanager:first_run().&lt;/span&gt;
&lt;span class='go'&gt;...&lt;/span&gt;
&lt;span class='go'&gt;{atomic,ok}&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)3&amp;gt; subsmanager:start_link().&lt;/span&gt;
&lt;span class='go'&gt;Waiting on mnesia tables..&lt;/span&gt;
&lt;span class='go'&gt;OK. Subscription table info:&lt;/span&gt;
&lt;span class='go'&gt;...snipped...&lt;/span&gt;
&lt;span class='go'&gt;{ok,&amp;lt;0.105.0&amp;gt;}&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)4&amp;gt; rr(&amp;quot;subsmanager.erl&amp;quot;).&lt;/span&gt;
&lt;span class='go'&gt;[state,subscription]&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)5&amp;gt; subsmanager:add_subscriptions([ #subscription{subscriber=alice, subscribee=rj} ]).&lt;/span&gt;
&lt;span class='go'&gt;ok&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)6&amp;gt; subsmanager:add_subscriptions([ #subscription{subscriber=bob, subscribee=rj} ]).&lt;/span&gt;
&lt;span class='go'&gt;ok&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)7&amp;gt; subsmanager:get_subscribers(rj).&lt;/span&gt;
&lt;span class='go'&gt;[bob,alice]&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)8&amp;gt; subsmanager:remove_subscriptions([ #subscription{subscriber=bob, subscribee=rj} ]).&lt;/span&gt;
&lt;span class='go'&gt;ok&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)9&amp;gt; subsmanager:get_subscribers(rj).&lt;/span&gt;
&lt;span class='go'&gt;[alice]&lt;/span&gt;
&lt;span class='go'&gt;(subsman@localhost)10&amp;gt; subsmanager:get_subscribers(charlie).&lt;/span&gt;
&lt;span class='go'&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;We&amp;#8217;ll use integer Ids to represent users for the benchmark - but for this test I used atoms (rj, alice, bob) and assumed that alice and bob are both on rj&amp;#8217;s friends list. It&amp;#8217;s nice that mnesia (and ets/dets) doesn&amp;#8217;t care what values you use - any Erlang term is valid. This means it&amp;#8217;s a simple upgrade to support multiple types of resource. You could start using &lt;code&gt;{user, 123}&lt;/code&gt; or &lt;code&gt;{photo, 789}&lt;/code&gt; to represent different things people might subscribe to, without changing anything in the subsmanager module.&lt;/p&gt;

&lt;h2 id='modifying_the_router_to_use_subscriptions'&gt;Modifying the router to use subscriptions&lt;/h2&gt;

&lt;p&gt;Instead of addressing messages to specific users, ie &lt;code&gt;router:send(123, &quot;Hello user 123&quot;)&lt;/code&gt;, we&amp;#8217;ll mark messages with a subject - that is, the person who generated the message (who played the song, who uploaded the photo etc) - and have the router deliver the message to every user who has subscribed to the subject user. In other words, the API will work like this: &lt;code&gt;router:send(123, &quot;Hello everyone subscribed to user 123&quot;)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Updated router.erl:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;router&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;behaviour&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;start_link&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;init&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_call&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_cast&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;handle_info&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
     &lt;span class='n'&gt;terminate&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;code_change&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='nb'&gt;send&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;login&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;logout&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;

&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nn'&gt;global&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;whereis_name&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;)).&lt;/span&gt;

&lt;span class='c'&gt;% will hold bidirectional mapping between id &amp;lt;--&amp;gt; pid&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;record&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nl'&gt;state&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;pid2id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;id2pid&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='nf'&gt;start_link&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start_link&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;global&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[],&lt;/span&gt; &lt;span class='p'&gt;[]).&lt;/span&gt;

&lt;span class='c'&gt;% sends Msg to anyone subscribed to Id&lt;/span&gt;
&lt;span class='nb'&gt;send&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nb'&gt;send&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='nf'&gt;login&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_pid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;login&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='nf'&gt;logout&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_pid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;call&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?SERVER&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;logout&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='c'&gt;%%&lt;/span&gt;

&lt;span class='nf'&gt;init&lt;/span&gt;&lt;span class='p'&gt;([])&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='c'&gt;% set this so we can catch death of logged in pids:&lt;/span&gt;
    &lt;span class='nb'&gt;process_flag&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;trap_exit&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;true&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='c'&gt;% use ets for routing tables&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nl'&gt;#state&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='n'&gt;pid2id&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;bag&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
                &lt;span class='n'&gt;id2pid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;bag&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;
               &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;login&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_pid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;insert&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.pid2id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;}),&lt;/span&gt;
    &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;insert&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.id2pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;}),&lt;/span&gt;
    &lt;span class='nb'&gt;link&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='c'&gt;% tell us if they exit, so we can log them out&lt;/span&gt;
    &lt;span class='c'&gt;%io:format(&amp;quot;~w logged in as ~w\n&amp;quot;,[Pid, Id]),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;logout&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nb'&gt;is_pid&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nb'&gt;unlink&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='nv'&gt;PidRows&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;lookup&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.pid2id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nv'&gt;PidRows&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
        &lt;span class='p'&gt;[]&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;_&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class='nv'&gt;IdRows&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;I&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;P&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;P&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='nv'&gt;I&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;PidRows&lt;/span&gt; &lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='c'&gt;% invert tuples&lt;/span&gt;
            &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;delete&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.pid2id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;   &lt;span class='c'&gt;% delete all pid-&amp;gt;id entries&lt;/span&gt;
            &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;delete_object&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.id2pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Obj&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;Obj&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;IdRows&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='c'&gt;% and all id-&amp;gt;pid&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='c'&gt;%io:format(&amp;quot;pid ~w logged out\n&amp;quot;,[Pid]),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;};&lt;/span&gt;

&lt;span class='nf'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nb'&gt;send&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='nv'&gt;F&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='c'&gt;% get users who are subscribed to Id:&lt;/span&gt;
        &lt;span class='nv'&gt;Users&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;subsmanager&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;get_subscribers&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Subscribers of &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt; = &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;Id&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Users&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='c'&gt;% get pids of anyone logged in from Users list:&lt;/span&gt;
        &lt;span class='nv'&gt;Pids0&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;lists&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;map&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
            &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;U&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;P&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;I&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;P&lt;/span&gt; &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nn'&gt;ets&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;lookup&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='nl'&gt;#state.id2pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;U&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
            &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
            &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;Id&lt;/span&gt; &lt;span class='p'&gt;|&lt;/span&gt; &lt;span class='nv'&gt;Users&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='c'&gt;% we are always subscribed to ourselves&lt;/span&gt;
        &lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nv'&gt;Pids&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;lists&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;flatten&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pids0&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Pids: &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Pids&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='c'&gt;% send Msg to them all&lt;/span&gt;
        &lt;span class='nv'&gt;M&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;router_msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
        &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='nv'&gt;M&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;Pids&lt;/span&gt; &lt;span class='p'&gt;],&lt;/span&gt;
        &lt;span class='c'&gt;% respond with how many users saw the message&lt;/span&gt;
        &lt;span class='nn'&gt;gen_server&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;reply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;From&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;length&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Pids&lt;/span&gt;&lt;span class='p'&gt;)})&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;F&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='c'&gt;% handle death and cleanup of logged in processes&lt;/span&gt;
&lt;span class='nf'&gt;handle_info&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Info&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nv'&gt;Info&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;&amp;#39;EXIT&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;Why&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class='n'&gt;handle_call&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='n'&gt;logout&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='n'&gt;blah&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='nv'&gt;Wtf&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Caught unhandled message: &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Wtf&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;

&lt;span class='nf'&gt;handle_cast&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Msg&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;noreply&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;span class='nf'&gt;terminate&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;Reason&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;span class='nf'&gt;code_change&lt;/span&gt;&lt;span class='p'&gt;(_&lt;/span&gt;&lt;span class='nv'&gt;OldVsn&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_&lt;/span&gt;&lt;span class='nv'&gt;Extra&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;State&lt;/span&gt;&lt;span class='p'&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;p&gt;And here&amp;#8217;s a quick test that doesn&amp;#8217;t require mochiweb - I&amp;#8217;ve used atoms instead of user ids, and omitted some output for clarity:&lt;/p&gt;
&lt;pre&gt;
(subsman@localhost)1&gt; c(subsmanager), c(router), rr(&quot;subsmanager.erl&quot;).
(subsman@localhost)2&gt; subsmanager:start_link().
(subsman@localhost)3&gt; router:start_link().
(subsman@localhost)4&gt; Subs = [#subscription{subscriber=alice, subscribee=rj}, #subscription{subscriber=bob, subscribee=rj}].
[#subscription{subscriber = alice,subscribee = rj},
#subscription{subscriber = bob,subscribee = rj}]
(subsman@localhost)5&gt; subsmanager:add_subscriptions(Subs).
ok
(subsman@localhost)6&gt; router:send(rj, &quot;RJ did something&quot;).
Subscribers of rj = [bob,alice]
Pids: []
{ok,0}
(subsman@localhost)7&gt; router:login(alice, self()).
ok
(subsman@localhost)8&gt; router:send(rj, &quot;RJ did something&quot;).
Subscribers of rj = [bob,alice]
Pids: [&amp;lt;0.46.0&amp;gt;]
{ok,1}
(subsman@localhost)9&gt; receive {router_msg, M} -&gt; io:format(&quot;~s\n&quot;,[M]) end.
RJ did something
ok
&lt;/pre&gt;
&lt;p&gt;This shows how alice can a receive a message when the subject is someone she is subscribed to (rj), even though the message wasn&amp;#8217;t sent directly to alice. The output shows that the router identified possible targets as &lt;code&gt;[alice,bob]&lt;/code&gt; but only delivered the message to one person, alice, because bob was not logged in.&lt;/p&gt;

&lt;h2 id='generating_a_typical_socialnetwork_friends_dataset'&gt;Generating a typical social-network friends dataset&lt;/h2&gt;

&lt;p&gt;We could generate lots of friend relationships at random, but that&amp;#8217;s not particularly realistic. Social networks tend to exhibit a power law distribution. Social networks usually have a few super-popular users (&lt;a href='http://twitter.com/barackobama'&gt;some Twitter users&lt;/a&gt; have over 100,000 followers) and plenty of people with just a handful of friends. The Last.fm friends data is typical - it fits a &lt;a href='http://en.wikipedia.org/wiki/Barab%C3%A1si-Albert_model'&gt;Barabási–Albert graph model&lt;/a&gt;, so that&amp;#8217;s what I&amp;#8217;ll use.&lt;/p&gt;

&lt;p&gt;To generate the dataset I&amp;#8217;m using the python module from the excellent &lt;a href='http://cneurocvs.rmki.kfki.hu/igraph/'&gt;igraph library&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;fakefriends.py:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='python'&gt;&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;igraph&lt;/span&gt;
&lt;span class='n'&gt;g&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;igraph&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Graph&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;Barabasi&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1000000&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;15&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;directed&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='bp'&gt;False&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;print&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Edges: &amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nb'&gt;str&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;g&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ecount&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s'&gt;&amp;quot; Verticies: &amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nb'&gt;str&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;g&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;vcount&lt;/span&gt;&lt;span class='p'&gt;())&lt;/span&gt;
&lt;span class='n'&gt;g&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;write_edgelist&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;fakefriends.txt&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This will generate with 2 user ids per line, space separated. These are the friend relationships we&amp;#8217;ll load into our subsmanager. User ids range from 1 to a million.&lt;/p&gt;

&lt;h2 id='bulk_loading_friends_data_into_mnesia'&gt;Bulk loading friends data into mnesia&lt;/h2&gt;

&lt;p&gt;This small module will read the fakefriends.txt file and create a list of subscription records.&lt;/p&gt;

&lt;p&gt;readfriends.erl - to read the fakefriends.txt and create subscription records:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;readfriends&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;export&lt;/span&gt;&lt;span class='p'&gt;([&lt;/span&gt;&lt;span class='n'&gt;load&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;]).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;record&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nl'&gt;subscription&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='p'&gt;}).&lt;/span&gt;

&lt;span class='nf'&gt;load&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Filename&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='n'&gt;for_each_line_in_file&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Filename&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Line&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Acc&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;As&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bs&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;string&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;tokens&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nn'&gt;string&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;strip&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Line&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;right&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='sc'&gt;$\n&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
            &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;string&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;to_integer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;As&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
            &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;string&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;to_integer&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Bs&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
            &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nl'&gt;#subscription&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;subscriber&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;A&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;subscribee&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='p'&gt;|&lt;/span&gt; &lt;span class='nv'&gt;Acc&lt;/span&gt; &lt;span class='p'&gt;]&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;read&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='p'&gt;[]).&lt;/span&gt;

&lt;span class='c'&gt;% via: http://www.trapexit.org/Reading_Lines_from_a_File&lt;/span&gt;
&lt;span class='nf'&gt;for_each_line_in_file&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Proc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Mode&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Accum0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;file&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;open&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Name&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Mode&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
    &lt;span class='n'&gt;for_each_line&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Proc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Accum0&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;for_each_line&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Proc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Accum&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;get_line&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
        &lt;span class='n'&gt;eof&lt;/span&gt;  &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;file&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;close&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='nv'&gt;Accum&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='nv'&gt;Line&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nv'&gt;NewAccum&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Proc&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Line&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Accum&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
                    &lt;span class='n'&gt;for_each_line&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Device&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Proc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;NewAccum&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now in the subsmanager shell, you can read from the text file and add the subscriptions:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; erl -name router@minifeeds4.gs2 +K &lt;span class='nb'&gt;true&lt;/span&gt; +A 128 -setcookie secretcookie -mnesia dump_log_write_threshold 50000 -mnesia dc_dump_limit 40
&lt;span class='go'&gt;erl&amp;gt; c(readfriends), c(subsmanager).&lt;/span&gt;
&lt;span class='go'&gt;erl&amp;gt; subsmanager:first_run().&lt;/span&gt;
&lt;span class='go'&gt;erl&amp;gt; subsmanager:start_link().&lt;/span&gt;
&lt;span class='go'&gt;erl&amp;gt; subsmanager:add_subscriptions( readfriends:load(&amp;quot;fakefriends.txt&amp;quot;) ).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Note the additional mnesia parameters - these are to avoid the &lt;b&gt;** WARNING ** Mnesia is overloaded&lt;/b&gt; messages you would (probably) otherwise see. Refer to my previous post: &lt;a href='http://www.metabrew.com/article/on-bulk-loading-data-into-mnesia/'&gt;On bulk loading data into Mnesia&lt;/a&gt; for alternative ways to load in lots of data. The best solution seems to be (as pointed out in the comments, thanks Jacob!) to set those options. The &lt;a href='http://www.erlang.org/doc/apps/mnesia/'&gt;Mnesia reference manual&lt;/a&gt; contains many other settings under Configuration Parameters, and is worth a look.&lt;/p&gt;

&lt;h2 id='turning_it_up_to_1_million'&gt;Turning it up to 1 Million&lt;/h2&gt;

&lt;p&gt;Creating a million tcp connections from one host is non-trivial. I&amp;#8217;ve a feeling that people who do this regularly have small clusters dedicated to simulating lots of client connections, probably running a real tool like Tsung. Even with the tuning from &lt;a href='http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1/'&gt;Part 1&lt;/a&gt; to increase kernel tcp memory, increase the file descriptor ulimits and set the local port range to the maximum, we will still hit a hard limit on ephemeral ports.&lt;/p&gt;

&lt;p&gt;When making a tcp connection, the client end is allocated (or you can specify) a port from the range in &lt;code&gt;/proc/sys/net/ipv4/ip_local_port_range&lt;/code&gt;. It doesn&amp;#8217;t matter if you specify it manually, or use an ephemeral port, you&amp;#8217;re still going to run out.&lt;/p&gt;

&lt;p&gt;In Part 1, we set the range to &amp;#8220;1024 65535&amp;#8221; - meaning there are 65535-1024 = 64511 unprivileged ports available. Some of them will be used by other processes, but we&amp;#8217;ll never get over 64511 client connections, because we&amp;#8217;ll run out of ports.&lt;/p&gt;

&lt;p&gt;The local port range is assigned per-IP, so if we make our outgoing connections specifically from a range of different local IP addresses, we&amp;#8217;ll be able to open more than 64511 outgoing connections in total.&lt;/p&gt;

&lt;p&gt;So let&amp;#8217;s bring up 17 new IP addresses, with the intention of making 62,000 connections from each - giving us a total of 1,054,000 connections. Safely over the 2^32 mark:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; &lt;span class='k'&gt;for &lt;/span&gt;i in &lt;span class='sb'&gt;`&lt;/span&gt;seq 1 17&lt;span class='sb'&gt;`&lt;/span&gt;; &lt;span class='k'&gt;do &lt;/span&gt;&lt;span class='nb'&gt;echo &lt;/span&gt;sudo ifconfig eth0:&lt;span class='nv'&gt;$i&lt;/span&gt; 10.0.0.&lt;span class='nv'&gt;$i&lt;/span&gt; up ; &lt;span class='k'&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you run &lt;code&gt;ifconfig&lt;/code&gt; now you should see your virtual interfaces: eth0:1, eth0:2 &amp;#8230; eth0:17, each with a different IP address. Obviously you should chose a sensible part of whatever address space you are using.&lt;/p&gt;

&lt;p&gt;All that remains now is to modify the &lt;code&gt;floodtest&lt;/code&gt; tool from Part 1 to specify the local IP it should connect from&amp;#8230; Unfortunately the &lt;a href='http://www.erlang.org/doc/man/http.html'&gt;erlang http client&lt;/a&gt; doesn&amp;#8217;t let you specify the source IP. Neither does ibrowse, the alternative http client library. Damn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Crazy Idea..&lt;/em&gt; At this point I considered another option: bringing up 17 pairs of IPs - one on the server and one on the client - each pair in their own isolated /30 subnet. I think that if I then made the client connect to any given server IP, it would force the local address to be other half of the pair on that subnet, because only one of the local IPs would actually be able to reach the server IP. In theory, this would mean declaring the local source IP on the client machine would not be necessary (although the range of server IPs would need to be specified). I don&amp;#8217;t know if this would really work - it sounded plausible at the time. In the end I decided it was too perverted and didn&amp;#8217;t try it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I also poked around in OTP&amp;#8217;s &lt;code&gt;http_transport&lt;/code&gt; code and considered adding support for specifying the local IP. It&amp;#8217;s not really a feature you usually need in an HTTP client though, and it would certainly have been more work.&lt;/p&gt;

&lt;p&gt;Note: &lt;code&gt;gen_tcp&lt;/code&gt; lets you specify the source address, so I ended up writing a rather crude client using &lt;code&gt;gen_tcp&lt;/code&gt; specifically for this test:&lt;/p&gt;

&lt;p&gt;floodtest2.erl&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;module&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;floodtest2&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;compile&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;export_all&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;SERVERADDR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;10.1.2.3&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt; &lt;span class='c'&gt;% where mochiweb is running&lt;/span&gt;
&lt;span class='p'&gt;-&lt;/span&gt;&lt;span class='ni'&gt;define&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;SERVERPORT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;8000&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='c'&gt;% Generate the config in bash like so (chose some available address space):&lt;/span&gt;
&lt;span class='c'&gt;% EACH=62000; for i in `seq 1 17`; do echo &amp;quot;{ {10,0,0,$i}, $((($i-1)*$EACH+1)), $(($i*$EACH)) }, &amp;quot;; done&lt;/span&gt;

&lt;span class='nf'&gt;run&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Interval&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nv'&gt;Config&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;62000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;62001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;124000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;124001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;186000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;186001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;248000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;248001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;310000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;310001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;372000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;7&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;372001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;434000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;8&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;434001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;496000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;9&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;496001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;558000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;558001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;620000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;11&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;620001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;682000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;12&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;682001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;744000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;13&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;744001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;806000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;14&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;806001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;868000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;15&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;868001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;930000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;16&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;930001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;992000&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;17&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='mi'&gt;992001&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;1054000&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
        &lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Config&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;

&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Config&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nv'&gt;Monitor&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;monitor&lt;/span&gt;&lt;span class='p'&gt;(),&lt;/span&gt;
        &lt;span class='nv'&gt;AdjustedInterval&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt; &lt;span class='o'&gt;/&lt;/span&gt; &lt;span class='nb'&gt;length&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Config&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fun&lt;/span&gt; &lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Lower&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Upper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Ip&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;AdjustedInterval&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;
          &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='nv'&gt;Ip&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Lower&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Upper&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;  &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nv'&gt;Config&lt;/span&gt; &lt;span class='p'&gt;],&lt;/span&gt;
        &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LowerID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;UpperID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;_,&lt;/span&gt; &lt;span class='p'&gt;_,&lt;/span&gt; &lt;span class='p'&gt;_)&lt;/span&gt; &lt;span class='k'&gt;when&lt;/span&gt; &lt;span class='nv'&gt;LowerID&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='nv'&gt;UpperID&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;done&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='nf'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LowerID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;UpperID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalIP&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;fun&lt;/span&gt; &lt;span class='n'&gt;connect&lt;/span&gt;&lt;span class='o'&gt;/&lt;/span&gt;&lt;span class='mi'&gt;5&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='no'&gt;?SERVERADDR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='no'&gt;?SERVERPORT&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalIP&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/test/&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='nv'&gt;LowerID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='k'&gt;receive&lt;/span&gt; &lt;span class='k'&gt;after&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;LowerID&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;UpperID&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;LocalIP&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Interval&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ServerAddr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ServerPort&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ClientIP&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Path&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nv'&gt;Opts&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;binary&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;packet&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ip&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ClientIP&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;reuseaddr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;true&lt;/span&gt;&lt;span class='p'&gt;},&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;active&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;false&lt;/span&gt;&lt;span class='p'&gt;}],&lt;/span&gt;
        &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;gen_tcp&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ServerAddr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ServerPort&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Opts&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nv'&gt;Monitor&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='n'&gt;open&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
        &lt;span class='nv'&gt;ReqL&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;io_lib&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;GET &lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\r\n&lt;/span&gt;&lt;span class='s'&gt;Host: &lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\r\n\r\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Path&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;ServerAddr&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='nv'&gt;Req&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;list_to_binary&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;ReqL&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='n'&gt;ok&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nn'&gt;gen_tcp&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='nb'&gt;send&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nv'&gt;Req&lt;/span&gt;&lt;span class='p'&gt;]),&lt;/span&gt;
        &lt;span class='n'&gt;do_recv&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;catch&lt;/span&gt; &lt;span class='nn'&gt;gen_tcp&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;close&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;)),&lt;/span&gt;
        &lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;do_recv&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='k'&gt;case&lt;/span&gt; &lt;span class='nn'&gt;gen_tcp&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;recv&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;of&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;ok&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class='nv'&gt;Monitor&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;bytes&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;size&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;)},&lt;/span&gt;
                        &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Recvd &lt;/span&gt;&lt;span class='si'&gt;~s&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nb'&gt;binary_to_list&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;)]),&lt;/span&gt;
                        &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Recvd &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='s'&gt; bytes&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='nb'&gt;size&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;)]),&lt;/span&gt;
                        &lt;span class='n'&gt;do_recv&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nv'&gt;Sock&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Monitor&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;error&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class='nv'&gt;Monitor&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                        &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
                &lt;span class='nv'&gt;Other&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class='nv'&gt;Monitor&lt;/span&gt; &lt;span class='o'&gt;!&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
                        &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Other:&lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;Other&lt;/span&gt;&lt;span class='p'&gt;])&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='c'&gt;% Monitor process receives stats and reports how much data we received etc:&lt;/span&gt;
&lt;span class='nb'&gt;monitor&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='nv'&gt;Pid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='no'&gt;?MODULE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;[{&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;}]),&lt;/span&gt;
        &lt;span class='nn'&gt;timer&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;send_interval&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt;
        &lt;span class='nv'&gt;Pid&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;

&lt;span class='nf'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nv'&gt;Open&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Chunks&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bytes&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='nv'&gt;S&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class='k'&gt;receive&lt;/span&gt;
                &lt;span class='n'&gt;report&lt;/span&gt;  &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='nn'&gt;io&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;format&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;{Open, Closed, Chunks, Bytes} = &lt;/span&gt;&lt;span class='si'&gt;~w&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,[&lt;/span&gt;&lt;span class='nv'&gt;S&lt;/span&gt;&lt;span class='p'&gt;]);&lt;/span&gt;
                &lt;span class='n'&gt;open&lt;/span&gt;    &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nv'&gt;Open&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Chunks&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bytes&lt;/span&gt;&lt;span class='p'&gt;});&lt;/span&gt;
                &lt;span class='n'&gt;closed&lt;/span&gt;  &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nv'&gt;Open&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Closed&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Chunks&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bytes&lt;/span&gt;&lt;span class='p'&gt;});&lt;/span&gt;
                &lt;span class='n'&gt;chunk&lt;/span&gt;   &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nv'&gt;Open&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Chunks&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bytes&lt;/span&gt;&lt;span class='p'&gt;});&lt;/span&gt;
                &lt;span class='p'&gt;{&lt;/span&gt;&lt;span class='n'&gt;bytes&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='o'&gt;-&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;monitor0&lt;/span&gt;&lt;span class='p'&gt;({&lt;/span&gt;&lt;span class='nv'&gt;Open&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Closed&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Chunks&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nv'&gt;Bytes&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='nv'&gt;B&lt;/span&gt;&lt;span class='p'&gt;})&lt;/span&gt;
        &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;As an initial test I was connecting to the mochiweb app from Part 1 - it simply sends one message to every client every 10 seconds.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;erl&amp;gt; c(floodtest2), floodtest2:run(20).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This quickly ate all my memory.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Turns out opening lots of connections with gen_tcp like that eats a lot of ram. I think it&amp;#8217;d need ~36GB to make it work without any additional tuning. I&amp;#8217;m not interested in trying to optimise my quick-hack erlang http client (in the real world, this would be 1M actual web browsers), and the only machine I could get my hands on that has more than 32GB of RAM is one of our production databases, and I can&amp;#8217;t find a good excuse to take Last.fm offline whilst I test this :) Additionally, it seems like it still only managed to open around 64,500 ports. Hmm.&lt;/p&gt;

&lt;p&gt;At this point I decided to break out the trusty &lt;a href='http://monkey.org/~provos/libevent/'&gt;libevent&lt;/a&gt;, which I was pleased to discover has an HTTP API. Newer versions also have a &lt;code&gt;evhttp_connection_set_local_address&lt;/code&gt; function in the http API. This sounds promising.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the http client in C using libevent:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='c'&gt;&lt;span class='cp'&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/time.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/queue.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;err.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;event.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;evhttp.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;netinet/in.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;time.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;pthread.h&amp;gt;&lt;/span&gt;

&lt;span class='cp'&gt;#define BUFSIZE 4096&lt;/span&gt;
&lt;span class='cp'&gt;#define NUMCONNS 62000&lt;/span&gt;
&lt;span class='cp'&gt;#define SERVERADDR &amp;quot;10.103.1.43&amp;quot;&lt;/span&gt;
&lt;span class='cp'&gt;#define SERVERPORT 8000&lt;/span&gt;
&lt;span class='cp'&gt;#define SLEEP_MS 10&lt;/span&gt;

&lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;BUFSIZE&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;

&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;bytes_recvd&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;chunks_recvd&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;connected&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

&lt;span class='c1'&gt;// called per chunk received&lt;/span&gt;
&lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;chunkcb&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;arg&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;s&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evbuffer_remove&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='n'&gt;input_buffer&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;BUFSIZE&lt;/span&gt; &lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='c1'&gt;//printf(&amp;quot;Read %d bytes: %s\n&amp;quot;, s, &amp;amp;buf);&lt;/span&gt;
    &lt;span class='n'&gt;bytes_recvd&lt;/span&gt; &lt;span class='o'&gt;+=&lt;/span&gt; &lt;span class='n'&gt;s&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;chunks_recvd&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;connected&lt;/span&gt; &lt;span class='o'&gt;&amp;gt;=&lt;/span&gt; &lt;span class='n'&gt;NUMCONNS&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class='n'&gt;chunks_recvd&lt;/span&gt;&lt;span class='o'&gt;%&lt;/span&gt;&lt;span class='mi'&gt;10000&lt;/span&gt;&lt;span class='o'&gt;==&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='n'&gt;printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&amp;gt;Chunks: %d&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;Bytes: %d&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;Closed: %d&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;chunks_recvd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;bytes_recvd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='c1'&gt;// gets called when request completes&lt;/span&gt;
&lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;reqcb&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;arg&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='nf'&gt;main&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;argc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;argv&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;event_init&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;evhttp_connection&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;evhttp_request&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='n'&gt;addr&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;16&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;
    &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;32&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt; &lt;span class='c1'&gt;// eg: &amp;quot;/test/123&amp;quot;&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='n'&gt;octet&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='k'&gt;for&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;octet&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='n'&gt;octet&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;=&lt;/span&gt;&lt;span class='mi'&gt;17&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt; &lt;span class='n'&gt;octet&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
        &lt;span class='n'&gt;sprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;addr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;10.224.0.%d&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;octet&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='k'&gt;for&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;=&lt;/span&gt;&lt;span class='n'&gt;NUMCONNS&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_connection&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evhttp_connection_new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;SERVERADDR&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;SERVERPORT&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_connection_set_local_address&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evhttp_connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;addr&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_set_timeout&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evhttp_connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;864000&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; &lt;span class='c1'&gt;// 10 day timeout&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evhttp_request_new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;reqcb&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_request&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='n'&gt;chunk_cb&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;chunkcb&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
            &lt;span class='n'&gt;sprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/test/%d&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='n'&gt;connected&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;%&lt;/span&gt;&lt;span class='mi'&gt;100&lt;/span&gt;&lt;span class='o'&gt;==&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;  &lt;span class='n'&gt;printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;Req: %s&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;%s&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;addr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;path&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_make_request&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;evhttp_connection&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;EVHTTP_REQ_GET&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;path&lt;/span&gt; &lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_connection_set_timeout&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evhttp_request&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='n'&gt;evcon&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;864000&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;event_loop&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;EVLOOP_NONBLOCK&lt;/span&gt; &lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;connected&lt;/span&gt; &lt;span class='o'&gt;%&lt;/span&gt; &lt;span class='mi'&gt;200&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt; &lt;span class='p'&gt;)&lt;/span&gt;
                &lt;span class='n'&gt;printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;Chunks: %d&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;Bytes: %d&lt;/span&gt;&lt;span class='se'&gt;\t&lt;/span&gt;&lt;span class='s'&gt;Closed: %d&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;chunks_recvd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;bytes_recvd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;closed&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;usleep&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;SLEEP_MS&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='mi'&gt;1000&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='n'&gt;event_dispatch&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Most parameters are hardcoded as #define&amp;#8217;s so you configure it by editing the source and recompiling.&lt;/p&gt;

&lt;p&gt;Compile and run:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; gcc -o httpclient httpclient.c -levent
&lt;span class='gp'&gt;$&lt;/span&gt; ./httpclient
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;This still failed to open more than 64,500 ports&lt;/em&gt;. Although it used less RAM doing it.&lt;/p&gt;

&lt;p&gt;It turns out that although I was specifying the local addresses, the ephemeral port allocation somewhere in the kernel or tcp stack didn&amp;#8217;t care, and still ran out after 2^16. So in order to open more than 64,500 connections, you need to specify the local address and local port yourself, and manage them accordingly.&lt;/p&gt;

&lt;p&gt;Unfortunately the libevent HTTP API doesn&amp;#8217;t have an option to specify the local port. I &lt;a href='http://monkeymail.org/archives/libevent-users/2008-November/001415.html'&gt;patched libevent&lt;/a&gt; to add a suitable function: &lt;code&gt;void evhttp_connection_set_local_port(struct evhttp_connection *evcon, u_short port);&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This was a surprisingly pleasant experience; libevent seems well written, and the documentation is pretty decent too.&lt;/p&gt;

&lt;p&gt;With my modified libevent installed, I was able to add the following under the set_local_address line in the above code: &lt;code&gt;evhttp_connection_set_local_port(evhttp_connection, 1024+i);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With that in place, multiple connections from different addresses were able to use the same local port number, specific to the the local address. I recompiled the client and let it run for a bit to confirm it would break the 2^16 barrier.&lt;/p&gt;
&lt;span title='Pun intended'&gt;Netstat confirms it&lt;/span&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;#&lt;/span&gt; netstat -n | awk &lt;span class='s1'&gt;&amp;#39;/^tcp/ {t[$NF]++}END{for(state in t){print state, t[state]} }&amp;#39;&lt;/span&gt;
&lt;span class='go'&gt;TIME_WAIT 8&lt;/span&gt;
&lt;span class='go'&gt;ESTABLISHED 118222&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This shows how many ports are open in various states. We&amp;#8217;re finally able to open more than 2^16 connections, phew.&lt;/p&gt;

&lt;p&gt;Now we have a tool capable of opening a million http connections from a single box. It seems to consume around 2KB per connection, plus whatever the kernel needs. It&amp;#8217;s time to use it for the &amp;#8220;million connected user&amp;#8221; test against our mochiweb comet server.&lt;/p&gt;

&lt;h2 id='c1024k_test__1_million_comet_connections'&gt;C1024K Test - 1 million comet connections&lt;/h2&gt;

&lt;p&gt;For this test I used 4 different servers of varying specs. These specs may be overpowered for the experiment, but they were available and waiting to go into production, and this made a good burn-in test. All four servers are on the same gigabit LAN, with up to 3 switches and a router in the middle somewhere.&lt;/p&gt;

&lt;p&gt;The 1 million test I ran is similar to the 10k test from parts 1 and 2, the main difference being the modified client, now written in C using libevent, and that I&amp;#8217;m running in a proper distributed-erlang setup with more than one machine.&lt;/p&gt;

&lt;p&gt;On server 1 - Quad-core 2GHz CPU, 16GB of RAM &lt;ul&gt;
	&lt;li&gt;Start subsmanager&lt;/li&gt;
	&lt;li&gt;Load in the friends data&lt;/li&gt;
	&lt;li&gt;Start the router&lt;/li&gt;
&lt;/ul&gt; On server 2 - Dual Quad-core 2.8GHz CPU, 32GB of RAM &lt;ul&gt;
	&lt;li&gt;Start mochiweb app&lt;/li&gt;
&lt;/ul&gt; On server 3 - Quad-core 2GHz CPU, 16GB of RAM &lt;ul&gt;
	&lt;li&gt;Create 17 virtual IPs as above&lt;/li&gt;
	&lt;li&gt;Install patched libevent&lt;/li&gt;
    &lt;li&gt;Run client: &lt;code&gt;./httpclient&lt;/code&gt; to create 100 connections per second, up to 1M&lt;/li&gt;
&lt;/ul&gt; On server 4 - Dual-core 2GHz, 2GB RAM &lt;ul&gt;
	&lt;li&gt;Run msggen program, to send lots of messages to the router&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;I measured the memory usage of mochiweb during the ramp-up to a million connections, and for the rest of the day:&lt;/p&gt;
&lt;a href='/images/2008/11/mochimem-c1000k.png'&gt;&lt;img alt='' class='aligncenter size-full wp-image-172' height='300' src='/images/2008/11/mochimem-c1000k.png' title='Mochiweb memory, 1M connections' width='500' /&gt;&lt;/a&gt;
&lt;p&gt;The httpclient has a built in delay of 10ms between connections, so it took nearly 3 hours to open a million connections. The resident memory used by the mochiweb process with 1M open connections was around 25GB. Here&amp;#8217;s the server this was running on as seen by Ganglia, which measures CPU, network and memory usage and produces nice graphs:&lt;/p&gt;
&lt;a href='/images/2008/11/server21.png'&gt;&lt;img alt='' class='alignnone size-medium wp-image-173' height='300' src='/images/2008/11/server21.png' title='Server running mochiweb, 1M connections' width='131' /&gt;&lt;/a&gt;
&lt;p&gt;You can see it needs around 38GB and has started to swap. I suspect the difference is mostly consumed by the kernel to keep those connections open. The uplift at the end is when I started sending messages.&lt;/p&gt;

&lt;p&gt;Messages were generated using 1,000 processes, with an average time between messages of 60ms per process, giving around 16,666 messages per second overall:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='erlang'&gt;&lt;span class='p'&gt;[&lt;/span&gt; &lt;span class='nb'&gt;spawn&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='k'&gt;fun&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='nn'&gt;msggen&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1000000&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='nn'&gt;random&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;uniform&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;100&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='mi'&gt;1000000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;||&lt;/span&gt; &lt;span class='nv'&gt;I&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;-&lt;/span&gt; &lt;span class='nn'&gt;lists&lt;/span&gt;&lt;span class='p'&gt;:&lt;/span&gt;&lt;span class='n'&gt;seq&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1000&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;].&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The machine (server-4) generating messages looked like this on Ganglia:&lt;/p&gt;
&lt;a href='/images/2008/11/msggen.png'&gt;&lt;img alt='' class='alignnone size-medium wp-image-170' height='300' src='/images/2008/11/msggen.png' title='16,666 msgs/sec' width='131' /&gt;&lt;/a&gt;
&lt;p&gt;That&amp;#8217;s 10 MB per second of messages it&amp;#8217;s pumping out - 16,666 messages a second. Typically these messages would come from a message bus, app servers, or part of an existing infrastructure.&lt;/p&gt;

&lt;p&gt;When I started sending messages, the load on server 1 (hosting subsmanager and router) stayed below 1, and CPU utilization increased from 0 to 5%.&lt;/p&gt;

&lt;p&gt;CPU on server 2 (hosting mochiweb app, with 1M connections) increased more dramatically:&lt;/p&gt;
&lt;a href='/images/2008/11/server2.png'&gt;&lt;img alt='' class='alignnone size-medium wp-image-171' height='300' src='/images/2008/11/server2.png' title='Mochiweb server' width='131' /&gt;&lt;/a&gt;
&lt;p&gt;Naturally as processes have to leave their hibernate state to handle messages, memory usage will increase slightly. Having all connections open with no messages is a best-case for memory usage - unsurprisingly, actually doing stuff requires more memory.&lt;/p&gt;

&lt;p&gt;So where does this leave us? To be on the safe side, the mochiweb machine would need 40GB of RAM to hold open 1M active comet connections. Under load, up to 30GB of the memory would be used by the mochiweb app, and the remaining 10GB by the kernel. In other words, you need to allow 40KB per connection.&lt;/p&gt;

&lt;p&gt;During various test with lots of connections, I ended up making some additional changes to my sysctl.conf. This was part trial-and-error, I don&amp;#8217;t really know enough about the internals to make especially informed decisions about which values to change. My policy was to wait for things to break, check &lt;code&gt;/var/log/kern.log&lt;/code&gt; and see what mysterious error was reported, then increase stuff that sounded sensible after a spot of googling. Here are the settings in place during the above test:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; cat /etc/sysctl.conf
&lt;span class='go'&gt;net.core.rmem_max = 33554432&lt;/span&gt;
&lt;span class='go'&gt;net.core.wmem_max = 33554432&lt;/span&gt;
&lt;span class='go'&gt;net.ipv4.tcp_rmem = 4096 16384 33554432&lt;/span&gt;
&lt;span class='go'&gt;net.ipv4.tcp_wmem = 4096 16384 33554432&lt;/span&gt;
&lt;span class='go'&gt;net.ipv4.tcp_mem = 786432 1048576 26777216&lt;/span&gt;
&lt;span class='go'&gt;net.ipv4.tcp_max_tw_buckets = 360000&lt;/span&gt;
&lt;span class='go'&gt;net.core.netdev_max_backlog = 2500&lt;/span&gt;
&lt;span class='go'&gt;vm.min_free_kbytes = 65536&lt;/span&gt;
&lt;span class='go'&gt;vm.swappiness = 0&lt;/span&gt;
&lt;span class='go'&gt;net.ipv4.ip_local_port_range = 1024 65535&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;I would like to learn more about Linux tcp tuning so I can make a more informed decision about these settings. These are almost certainly not optimal, but at least they were enough to get to 1M connections. These changes, along with the fact this is running on a 64bit Erlang VM, and thus has a wordsize of 8bytes instead of 4, might explain why the memory usage is much higher than I observed during the C10k test of part 2.&lt;/p&gt;

&lt;h2 id='an_erlang_cnode_using_libevent'&gt;An Erlang C-Node using Libevent&lt;/h2&gt;

&lt;p&gt;After dabbling with the HTTP api for libevent, it seemed entirely sensible to try the 1M connection test against a libevent HTTPd written in C so we have a basis for comparison.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m guessing that enabling kernel poll means the erlang VM is able to use epoll (or similar), but even so there&amp;#8217;s clearly some overhead involved which we might be able to mitigate by delegating the connection handling to a C program using libevent. I want to reuse most of the Erlang code so far, so let&amp;#8217;s do the bare minimum in C - just the connection handling and HTTP stuff.&lt;/p&gt;

&lt;p&gt;Libevent has an asynchronous HTTP API, which makes implementing http servers trivial - well, trivial for C, but still less trivial than mochiweb IMO ;) I&amp;#8217;d also been looking for an excuse to try the Erlang C interface, so the following program combines the two. It&amp;#8217;s a comet http server in C using libevent which identifies users using an integer Id (like our mochiweb app), and also acts as an Erlang C-Node.&lt;/p&gt;

&lt;p&gt;It connects to a designated erlang node, listens for messages like &lt;code&gt;{ 123, &amp;lt;&amp;lt;&quot;Hello user 123&quot;&amp;gt;&amp;gt; }&lt;/code&gt; then dispatches &amp;#8220;Hello user 123&amp;#8221; to user 123, if connected. Messages for users that are not connected are discarded, just like previous examples.&lt;/p&gt;

&lt;p&gt;httpdcnode.c&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='c'&gt;&lt;span class='cp'&gt;#include &amp;lt;sys/types.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/time.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/queue.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;err.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;event.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;evhttp.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;sys/socket.h&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;lt;netinet/in.h&amp;gt;&lt;/span&gt;

&lt;span class='cp'&gt;#include &amp;quot;erl_interface.h&amp;quot;&lt;/span&gt;
&lt;span class='cp'&gt;#include &amp;quot;ei.h&amp;quot;&lt;/span&gt;

&lt;span class='cp'&gt;#include &amp;lt;pthread.h&amp;gt;&lt;/span&gt;

&lt;span class='cp'&gt;#define BUFSIZE 1024&lt;/span&gt;
&lt;span class='cp'&gt;#define MAXUSERS (17*65536) &lt;/span&gt;&lt;span class='c1'&gt;// C1024K&lt;/span&gt;

&lt;span class='c1'&gt;// List of current http requests by uid:&lt;/span&gt;
&lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;clients&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;MAXUSERS&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;
&lt;span class='c1'&gt;// Memory to store uids passed to the cleanup callback:&lt;/span&gt;
&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;slots&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;MAXUSERS&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;

&lt;span class='c1'&gt;// called when user disconnects&lt;/span&gt;
&lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;cleanup&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_connection&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;evcon&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;arg&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;uidp&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='n'&gt;arg&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;fprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stderr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;disconnected uid %d&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;uidp&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;clients&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;uidp&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='c1'&gt;// handles http connections, sets them up for chunked transfer,&lt;/span&gt;
&lt;span class='c1'&gt;// extracts the user id and registers in the global connection table,&lt;/span&gt;
&lt;span class='c1'&gt;// also sends a welcome chunk.&lt;/span&gt;
&lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;request_handler&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp_request&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;arg&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evbuffer&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='n'&gt;buf&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evbuffer_new&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
        &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
            &lt;span class='n'&gt;err&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;failed to create response buffer&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;

        &lt;span class='n'&gt;evhttp_add_header&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='n'&gt;output_headers&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;text/html; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

        &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;uid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;strncmp&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evhttp_request_uri&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;/test/&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
            &lt;span class='n'&gt;uid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;atoi&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='mi'&gt;6&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='n'&gt;evhttp_request_uri&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;

        &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
            &lt;span class='n'&gt;evbuffer_add_printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;User id not found, try /test/123 instead&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_send_reply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;HTTP_NOTFOUND&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Not Found&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evbuffer_free&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='k'&gt;return&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;

        &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt; &lt;span class='o'&gt;&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;MAXUSERS&lt;/span&gt;&lt;span class='p'&gt;){&lt;/span&gt;
            &lt;span class='n'&gt;evbuffer_add_printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Max uid allowed is %d&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;MAXUSERS&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evhttp_send_reply&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;HTTP_SERVUNAVAIL&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;We ran out of numbers&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='n'&gt;evbuffer_free&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='k'&gt;return&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;

        &lt;span class='n'&gt;evhttp_send_reply_start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;HTTP_OK&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='c1'&gt;// Send welcome chunk:&lt;/span&gt;
        &lt;span class='n'&gt;evbuffer_add_printf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Welcome, Url: &amp;#39;%s&amp;#39; Id: %d&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;evhttp_request_uri&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;),&lt;/span&gt; &lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;evhttp_send_reply_chunk&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;evbuffer_free&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

        &lt;span class='c1'&gt;// put reference into global uid-&amp;gt;connection table:&lt;/span&gt;
        &lt;span class='n'&gt;clients&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='c1'&gt;// set close callback&lt;/span&gt;
        &lt;span class='n'&gt;evhttp_connection_set_closecb&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt; &lt;span class='n'&gt;req&lt;/span&gt;&lt;span class='o'&gt;-&amp;gt;&lt;/span&gt;&lt;span class='n'&gt;evcon&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;cleanup&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;slots&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt; &lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;


&lt;span class='c1'&gt;// runs in a thread - the erlang c-node stuff&lt;/span&gt;
&lt;span class='c1'&gt;// expects msgs like {uid, msg} and sends a a &amp;#39;msg&amp;#39; chunk to uid if connected&lt;/span&gt;
&lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;cnode_run&lt;/span&gt;&lt;span class='p'&gt;()&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;fd&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;                                  &lt;span class='cm'&gt;/* fd to Erlang node */&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;got&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;                                 &lt;span class='cm'&gt;/* Result of receive */&lt;/span&gt;
    &lt;span class='kt'&gt;unsigned&lt;/span&gt; &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;BUFSIZE&lt;/span&gt;&lt;span class='p'&gt;];&lt;/span&gt;              &lt;span class='cm'&gt;/* Buffer for incoming message */&lt;/span&gt;
    &lt;span class='n'&gt;ErlMessage&lt;/span&gt; &lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;                         &lt;span class='cm'&gt;/* Incoming message */&lt;/span&gt;

    &lt;span class='n'&gt;ETERM&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

    &lt;span class='n'&gt;erl_init&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;erl_connect_init&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;secretcookie&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='n'&gt;erl_err_quit&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;erl_connect_init&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='n'&gt;fd&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;erl_connect&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;httpdmaster@localhost&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;))&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
        &lt;span class='n'&gt;erl_err_quit&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;erl_connect&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='n'&gt;fprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stderr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Connected to httpdmaster@localhost&lt;/span&gt;&lt;span class='se'&gt;\n\r&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evbuffer&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;evbuf&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;

    &lt;span class='k'&gt;while&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;got&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;erl_receive_msg&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;fd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;buf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;BUFSIZE&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
        &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;got&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;ERL_TICK&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='k'&gt;continue&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='k'&gt;else&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;got&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;ERL_ERROR&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='n'&gt;fprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stderr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;ERL_ERROR from erl_receive_msg.&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='k'&gt;break&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt; &lt;span class='k'&gt;else&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
            &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='n'&gt;type&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;ERL_REG_SEND&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='p'&gt;{&lt;/span&gt;
                &lt;span class='c1'&gt;// get uid and body data from eg: {123, &amp;lt;&amp;lt;&amp;quot;Hello&amp;quot;&amp;gt;&amp;gt;}&lt;/span&gt;
                &lt;span class='n'&gt;uid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;erl_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='n'&gt;msg&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;erl_element&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;userid&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ERL_INT_VALUE&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;body&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='n'&gt;ERL_BIN_PTR&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;body_len&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ERL_BIN_SIZE&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='c1'&gt;// Is this userid connected?&lt;/span&gt;
                &lt;span class='k'&gt;if&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;clients&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;userid&lt;/span&gt;&lt;span class='p'&gt;]){&lt;/span&gt;
                    &lt;span class='n'&gt;fprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stderr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Sending %d bytes to uid %d&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;body_len&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;userid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;                
                    &lt;span class='n'&gt;evbuf&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evbuffer_new&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
                    &lt;span class='n'&gt;evbuffer_add&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evbuf&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='k'&gt;const&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='n'&gt;body&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kt'&gt;size_t&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='n'&gt;body_len&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                    &lt;span class='n'&gt;evhttp_send_reply_chunk&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;clients&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;userid&lt;/span&gt;&lt;span class='p'&gt;],&lt;/span&gt; &lt;span class='n'&gt;evbuf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                    &lt;span class='n'&gt;evbuffer_free&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;evbuf&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='p'&gt;}&lt;/span&gt;&lt;span class='k'&gt;else&lt;/span&gt;&lt;span class='p'&gt;{&lt;/span&gt;
                    &lt;span class='n'&gt;fprintf&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;stderr&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;Discarding %d bytes to uid %d - user not connected&lt;/span&gt;&lt;span class='se'&gt;\n&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
                            &lt;span class='n'&gt;body_len&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;userid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;                
                    &lt;span class='c1'&gt;// noop&lt;/span&gt;
                &lt;span class='p'&gt;}&lt;/span&gt;
                &lt;span class='n'&gt;erl_free_term&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;emsg&lt;/span&gt;&lt;span class='p'&gt;.&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
                &lt;span class='n'&gt;erl_free_term&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;uid&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt; 
                &lt;span class='n'&gt;erl_free_term&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;msg&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
            &lt;span class='p'&gt;}&lt;/span&gt;
        &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='p'&gt;}&lt;/span&gt;
    &lt;span class='c1'&gt;// if we got here, erlang connection died.&lt;/span&gt;
    &lt;span class='c1'&gt;// this thread is supposed to run forever&lt;/span&gt;
    &lt;span class='c1'&gt;// TODO - gracefully handle failure / reconnect / etc&lt;/span&gt;
    &lt;span class='n'&gt;pthread_exit&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;

&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='nf'&gt;main&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;argc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='kt'&gt;char&lt;/span&gt; &lt;span class='o'&gt;**&lt;/span&gt;&lt;span class='n'&gt;argv&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='p'&gt;{&lt;/span&gt;
    &lt;span class='c1'&gt;// Launch the thread that runs the cnode:&lt;/span&gt;
    &lt;span class='n'&gt;pthread_attr_t&lt;/span&gt; &lt;span class='n'&gt;tattr&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;pthread_t&lt;/span&gt; &lt;span class='n'&gt;helper&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;status&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;pthread_create&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;&amp;amp;&lt;/span&gt;&lt;span class='n'&gt;helper&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;cnode_run&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;

    &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='k'&gt;for&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;&amp;lt;=&lt;/span&gt;&lt;span class='n'&gt;MAXUSERS&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='o'&gt;++&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='n'&gt;slots&lt;/span&gt;&lt;span class='p'&gt;[&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='p'&gt;]&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='n'&gt;i&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='c1'&gt;// Launch libevent httpd:&lt;/span&gt;
    &lt;span class='k'&gt;struct&lt;/span&gt; &lt;span class='n'&gt;evhttp&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;httpd&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;event_init&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='n'&gt;httpd&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;evhttp_start&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='mi'&gt;8000&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;evhttp_set_gencb&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;httpd&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;request_handler&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='nb'&gt;NULL&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='n'&gt;event_dispatch&lt;/span&gt;&lt;span class='p'&gt;();&lt;/span&gt;
    &lt;span class='c1'&gt;// Not reached, event_dispatch() shouldn&amp;#39;t return&lt;/span&gt;
    &lt;span class='n'&gt;evhttp_free&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;httpd&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
    &lt;span class='k'&gt;return&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
&lt;span class='p'&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The maximum number of users is #defined, and similarly to the mochiweb server, it listens on port 8000 and expects users to connect with a path like so: &amp;lt;code&amp;gt;/test/&amp;lt;userid&amp;gt;&amp;lt;/code&amp;gt;. Also hardcoded is the name of the erlang node it will connect to in order to receive messages, &lt;code&gt;httpdmaster@localhost&lt;/code&gt;, and the erlang cookie, &amp;#8220;secretcookie&amp;#8221;. Change these accordingly.&lt;/p&gt;

&lt;p&gt;Run the erlang node it will connect to first:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; erl -setcookie secretcookie -sname httpdmaster@localhost
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Compile and run like so:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='gp'&gt;$&lt;/span&gt; gcc -o httpdcnode httpdcnode.c -lerl_interface -lei -levent
&lt;span class='gp'&gt;$&lt;/span&gt; ./httpdcnode
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In the erlang shell, check you can see the hidden c-node:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;code class='console'&gt;&lt;span class='go'&gt;erl&amp;gt; nodes(hidden).&lt;/span&gt;
&lt;span class='go'&gt;[c1@localhost]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now connect in your browser to &lt;code&gt;http://localhost:8000/test/123&lt;/code&gt;. You should see the welcome message.&lt;/p&gt;

&lt;p&gt;Now back to the erlang shell - send a message to the C node:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;erl&gt; {any, c1@localhost} ! {123, &amp;lt;&amp;lt;&quot;Hello Libevent World&quot;&amp;gt;&amp;gt;}.&lt;/code&gt;&lt;/p&gt;
&lt;em&gt;Note that we don't have a Pid to use, so we use the alternate representation of {procname, node}. We use 'any' as the process name, which is ignored by the C-node.&lt;/em&gt;&lt;b&gt;Now you're able to deliver comet messages via Erlang, but all the http connections are managed by a libevent C program which acts as an Erlang node.&lt;/b&gt;
&lt;p&gt;After removing the debug print statements, I connected 1M clients to the httpdcnode server using the same client as above, the machine showed a total of just under 10GB or memory used. The resident memory of the server process was stable at under 2GB:&lt;/p&gt;
&lt;a href='/images/2008/11/mochimem-libevent.png'&gt;&lt;img alt='' class='aligncenter size-full wp-image-176' height='300' src='/images/2008/11/mochimem-libevent.png' title='Memory of libevent-based server process, 1M connections' width='500' /&gt;&lt;/a&gt;
&lt;p&gt;So big savings compared to mochiweb when handling lots of connections - the resident memory per connection for the server process with libevent is just under 2KB. With everything connected, the server machine claims: &lt;code&gt;Mem:  32968672k total,  9636488k used, 23332184k free,      180k buffers&lt;/code&gt; So the kernel/tcp stack is consuming an additional 8KB per connection, which seems a little high, but I have no basis for comparison.&lt;/p&gt;

&lt;p&gt;This libevent-cnode server needs a bit more work. It doesn&amp;#8217;t sensibly handle multiple connections from the same user yet, and there&amp;#8217;s no locking so a race condition exists if you disconnect at just when a message was going to be dispatched.&lt;/p&gt;

&lt;p&gt;Even so, I think this could be generalized in such a way that would allow you to &lt;strong&gt;use Erlang for all the interesting stuff, and have a C+libevent process act as a dumb connection-pool&lt;/strong&gt;. With a bit more wrapper code and callbacks into Erlang, you&amp;#8217;d hardly need to know this was going on - the C program could be run as a driver or a C-node, and an Erlang wrapper could give you a decent api built on top of libevent. (see &lt;a href='http://www.metabrew.com/article/erlang-libketama-driver-consistent-hashing/'&gt;this post&lt;/a&gt; for an example Erlang C driver). I would like to experiment further with this.&lt;/p&gt;

&lt;h2 id='final_thoughts'&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;I have enough data now to judge how much hardware would be needed if we deploy a large scale comet system for Last.fm. Even a worst case of 40KB per connection isn&amp;#8217;t unreasonable - memory is pretty cheap at the moment, and 40GB to support a million users is not unreasonable. 10GB is even better. I will finish up the app I&amp;#8217;m building and deploy it somewhere people can try it out. Along the way I&amp;#8217;ll tidy up the erlang memcached client I&amp;#8217;m using and release that (from jungerl, with modifications for consistent hashing and some bug fixes), and some other things. Stay tuned :)&lt;/p&gt;</description>
    </item>
    

  </channel> 
</rss>
