Skip to content

Instantly share code, notes, and snippets.

@drslump
Created November 20, 2013 20:52
Show Gist options
  • Save drslump/7570805 to your computer and use it in GitHub Desktop.
Save drslump/7570805 to your computer and use it in GitHub Desktop.
proof of concept for a Nailgun server in .Net
"""
boo-daemon is a tool to speed up the start up of .Net applications
It keeps itself running in the background listening for commands
on a tcp socket. When running an application it communicates with
the background process to actually execute the program in a
warmed up AppDomain.
BOO_DAEMON_PORT=8888
BOO_DAEMON_MEMORY=50M
BOO_DAEMON_LIFE=2h
boo-daemon --port=8888 --memory=50M --life=2h booc.exe -v file.boo
boo-daemon --life=+30m booi.exe --debug program.boo
boo-daemon --port=8888 --server
boo-daemon --port=8888 --kill
NOTE: Implementing the command in .net/mono incurs in an overhead,
since the protocol is compatible with Nailgun we can reuse
the ng client.
"""
namespace boodaemon
import System
import System.Reflection
from System.Collections import DictionaryEntry
from System.Net import IPEndPoint, IPAddress
from System.Net.Sockets import TcpListener, TcpClient
from System.Diagnostics import Stopwatch
from System.IO import BinaryReader, BinaryWriter, EndOfStreamException, IOException
class AppDomainRunner(MarshalByRefObject):
property Out as IO.StringWriter
property Error as IO.StringWriter
def constructor():
pass
def Run(program as string, argv as (string)) as int:
# Replace console streams
backupOut = Console.Out
Out = IO.StringWriter()
Console.SetOut(Out)
backupErr = Console.Error
Error = IO.StringWriter()
Console.SetError(Error)
try:
asm = Reflection.Assembly.LoadFrom(program)
print asm
main = asm.EntryPoint
if not main:
raise "Entry point not found"
print 'args:', join(argv, ', ')
return main.Invoke(null, (argv,))
ensure:
# Restore console streams
Console.SetOut(backupOut)
Console.SetError(backupErr)
def CreateRunnerInAppDomain(domain as AppDomain):
assemblies = AppDomain.CurrentDomain.GetAssemblies()
for asm in assemblies:
print "Preloaded assemblies: $(asm.FullName)"
domain.AssemblyResolve += def (sender, args as ResolveEventArgs):
print "Resolving: $(args.Name)"
return null
runner as AppDomainRunner = domain.CreateInstanceAndUnwrap(
typeof(AppDomainRunner).Assembly.FullName,
typeof(AppDomainRunner).FullName,
false,
BindingFlags.Public | BindingFlags.Instance,
null,
(,),
null,
null
)
return runner
def server():
listener = TcpListener(IPAddress.Loopback, 8888)
listener.Start()
print "TCP server started"
domain = AppDomain.CreateDomain("CompilerDomain")
while true:
# Wait for a new connection
conn = listener.AcceptTcpClient()
print "New connection!"
stream = conn.GetStream()
br = BinaryReader(stream)
bw = BinaryWriter(stream)
# Reset the current environment variables
for key in Environment.GetEnvironmentVariables().Keys:
Environment.SetEnvironmentVariable(key, null)
args = []
while true:
try:
chunk = ParseChunk(br)
# print "Type: $(chunk.Type) -- $(chunk.Data)"
if chunk.Type == ChunkType.Argument:
args.Add(chunk.Data)
elif chunk.Type == ChunkType.Environment:
key, value = chunk.Data.Split((char('='),), 2)
Environment.SetEnvironmentVariable(key, value)
elif chunk.Type == ChunkType.WorkingDirectory:
System.IO.Directory.SetCurrentDirectory(chunk.Data)
elif chunk.Type == ChunkType.Command:
try:
runner = CreateRunnerInAppDomain(domain)
exitCode = runner.Run(chunk.Data, array(string, args))
chunk = Chunk(ChunkType.Stdout, runner.Out.ToString())
SerializeChunk(chunk, bw)
chunk = Chunk(ChunkType.Stderr, runner.Error.ToString())
SerializeChunk(chunk, bw)
except ex:
chunk = Chunk(ChunkType.Stderr, "Error running assembly: $ex")
SerializeChunk(chunk, bw)
exitCode = 128
chunk = Chunk(ChunkType.Exit, exitCode.ToString())
SerializeChunk(chunk, bw)
bw.Flush()
break
else:
print "Type: $(chunk.Type) -- $(chunk.Data)"
except ex as EndOfStreamException:
print "EOS"
break
except ex as IOException:
print "IO error: $ex"
break
server()
"""
Protocol should be compatible with Nailgun (http://www.martiansoftware.com/nailgun/protocol.html)
"""
namespace boodaemon
from System import UInt32
from System.IO import BinaryReader, BinaryWriter
from System.Net.IPAddress import NetworkToHostOrder, HostToNetworkOrder
from System.Text import ASCIIEncoding
enum ChunkType:
Argument
Environment
WorkingDirectory
Command
Stdin
Stdout
Stderr
InputStart
InputEnd
Exit
Heartbeat
class Chunk:
static public mapping = {
ChunkType.Argument: 'A',
ChunkType.Environment: 'E',
ChunkType.WorkingDirectory: 'D',
ChunkType.Command: 'C',
ChunkType.Stdin: '0',
ChunkType.Stdout: '1',
ChunkType.Stderr: '2',
ChunkType.InputStart: 'S',
ChunkType.InputEnd: '.',
ChunkType.Exit: 'X',
ChunkType.Heartbeat: 'H'
}
property Type as ChunkType
property Data as string
def constructor(type as ChunkType):
Type = type
def constructor(type as ChunkType, data as string):
self(type)
Data = data
def ByteToChunkType(type as byte) as ChunkType:
strtype = ASCIIEncoding.ASCII.GetString((type,))
for pair in Chunk.mapping:
if pair.Value == strtype:
return pair.Key
raise "Unsupported chunk type: $type"
def ChunkTypeToByte(type as ChunkType) as byte:
strtype as string = Chunk.mapping[type]
return ASCIIEncoding.ASCII.GetBytes(strtype)[0]
def ParseChunk(br as BinaryReader):
size as uint = NetworkToHostOrder(br.ReadInt32())
type as byte = br.ReadByte()
data as string = null
if size > 0:
bytes = br.ReadBytes(size)
data = ASCIIEncoding().GetString(bytes, 0, bytes.Length)
return Chunk(ByteToChunkType(type), data)
def SerializeChunk(chunk as Chunk, bw as BinaryWriter):
if chunk.Data != null:
buffer = ASCIIEncoding().GetBytes(chunk.Data)
else:
buffer = array(byte, 0)
# Avoid runtime complaints about negative numbers being casted to unsigned
# unchecked:
# bw.Write(HostToNetworkOrder(buffer.Length) cast uint)
size = array(byte, 4)
size[0] = (buffer.Length >> 24) & 0xff
size[1] = (buffer.Length >> 16) & 0xff
size[2] = (buffer.Length >> 8) & 0xff
size[3] = buffer.Length & 0xff;
bw.Write(size)
bw.Write(ChunkTypeToByte(chunk.Type))
bw.Write(buffer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment