Created
November 20, 2013 20:52
-
-
Save drslump/7570805 to your computer and use it in GitHub Desktop.
proof of concept for a Nailgun server in .Net
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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