1 module libasync.dns; 2 3 import libasync.types; 4 import libasync.events; 5 import core.thread : Thread, ThreadGroup; 6 import core.sync.mutex; 7 import core.sync.condition; 8 import core.atomic; 9 import libasync.threads; 10 11 enum DNSCmd { 12 RESOLVEHOST, 13 RESOLVEIP 14 } 15 16 /// Resolves internet addresses and returns the results in a specified callback. 17 shared final class AsyncDNS 18 { 19 nothrow: 20 private: 21 EventLoop m_evLoop; 22 bool m_busy; 23 bool m_error; 24 DNSReadyHandler m_handler; 25 DNSCmdInfo m_cmdInfo; 26 StatusInfo m_status; 27 Thread m_owner; 28 29 public: 30 this(EventLoop evl) { 31 m_evLoop = cast(shared) evl; 32 try m_cmdInfo.ready = new shared AsyncSignal(cast(EventLoop)m_evLoop); catch { assert(false, "Failed to start DNS Signaling"); } 33 m_cmdInfo.ready.run(cast(void delegate())&callback); 34 m_owner = cast(shared)Thread.getThis(); 35 try m_cmdInfo.mtx = cast(shared) new Mutex; catch {} 36 } 37 38 synchronized @property StatusInfo status() const 39 { 40 return cast(StatusInfo) m_status; 41 } 42 43 @property string error() const 44 { 45 return status.text; 46 } 47 48 /// Uses the callback for all resolved addresses. 49 shared(typeof(this)) handler(void delegate(NetworkAddress) del) { 50 shared DNSReadyHandler handler; 51 handler.del = cast(shared) del; 52 handler.ctxt = this; 53 try synchronized(this) m_handler = handler; catch { assert(false, "Failed to set handler in AsyncDNS"); } 54 return this; 55 } 56 57 /// Sends a request through a thread pool for the specified host to be resolved. The 58 /// callback specified in run() will be signaled with the OS-specific NetworkAddress 59 /// structure. 60 bool resolveHost(string url, bool ipv6 = false) 61 in { 62 assert(!m_busy, "Resolver is busy or closed"); 63 assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on."); 64 } 65 body { 66 try synchronized(m_cmdInfo.mtx) { 67 m_cmdInfo.command = DNSCmd.RESOLVEHOST; 68 m_cmdInfo.ipv6 = ipv6; 69 m_cmdInfo.url = cast(shared) url; 70 } catch {} 71 72 return sendCommand(); 73 } 74 75 /// Returns an OS-specific NetworkAddress structure from the specified IP. 76 NetworkAddress resolveIP(string url, bool ipv6) 77 in { 78 assert(!m_busy, "Resolver is busy or closed"); 79 assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on."); 80 } 81 body { 82 return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no); 83 } 84 85 // chooses a thread or starts it if necessary 86 private bool sendCommand() 87 in { assert(!waiting, "File is busy or closed"); } 88 body { 89 waiting = true; 90 m_error = false; 91 status = StatusInfo.init; 92 93 Waiter cmd_handler; 94 try { 95 cmd_handler = popWaiter(); 96 } catch (Throwable e) { 97 import std.stdio; 98 try { 99 status = StatusInfo(Status.ERROR, e.toString()); 100 m_error = true; 101 } catch {} 102 103 return false; 104 105 } 106 assert(cmd_handler.cond); 107 m_cmdInfo.waiter = cast(shared)cmd_handler; 108 try { 109 synchronized(gs_wlock) 110 gs_jobs.insert(CommandInfo(CmdInfoType.DNS, cast(void*) this)); 111 112 cmd_handler.cond.notifyAll(); 113 } 114 catch (Exception e){ 115 import std.stdio; 116 try writeln("Exception occured notifying foreign thread: ", e); catch {} 117 } 118 return true; 119 } 120 121 /// Cleans up underlying resources. Used as a placeholder for possible future purposes. 122 bool kill() { 123 return true; 124 } 125 126 package: 127 synchronized @property DNSCmdInfo cmdInfo() { 128 return m_cmdInfo; 129 } 130 131 132 shared(NetworkAddress*) addr() { 133 try synchronized(m_cmdInfo.mtx) 134 return cast(shared)&m_cmdInfo.addr; 135 catch {} 136 return null; 137 } 138 139 synchronized @property void status(StatusInfo stat) { 140 m_status = cast(shared) stat; 141 } 142 143 synchronized @property bool waiting() const { 144 return cast(bool) m_busy; 145 } 146 147 synchronized @property void waiting(bool b) { 148 m_busy = cast(shared) b; 149 } 150 151 void callback() { 152 153 try { 154 m_handler(cast(NetworkAddress)m_cmdInfo.addr); 155 } 156 catch (Throwable e) { 157 import std.stdio : writeln; 158 try writeln("Failed to send command. ", e.toString()); catch {} 159 } 160 } 161 162 } 163 164 package shared struct DNSCmdInfo 165 { 166 DNSCmd command; 167 bool ipv6; 168 string url; 169 NetworkAddress addr; 170 Waiter waiter; 171 AsyncSignal ready; 172 AsyncDNS dns; 173 Mutex mtx; // for NetworkAddress writing 174 } 175 176 package shared struct DNSReadyHandler { 177 AsyncDNS ctxt; 178 void delegate(NetworkAddress) del; 179 180 void opCall(NetworkAddress addr) { 181 assert(ctxt !is null); 182 del(addr); 183 return; 184 } 185 }