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, bool force_async = 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 if (force_async == true) { 67 try synchronized(m_cmdInfo.mtx) { 68 m_cmdInfo.command = DNSCmd.RESOLVEHOST; 69 m_cmdInfo.ipv6 = ipv6; 70 m_cmdInfo.url = cast(shared) url; 71 } catch {} 72 } else { 73 m_cmdInfo.command = DNSCmd.RESOLVEHOST; 74 m_cmdInfo.ipv6 = ipv6; 75 m_cmdInfo.url = cast(shared) url; 76 m_cmdInfo.addr = cast(shared)( (cast(EventLoop)m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no) ); 77 callback(); 78 return true; 79 } 80 81 return sendCommand(); 82 } 83 84 /// Returns an OS-specific NetworkAddress structure from the specified IP. 85 NetworkAddress resolveIP(string url, bool ipv6) 86 in { 87 assert(!m_busy, "Resolver is busy or closed"); 88 assert(m_handler.ctxt !is null, "AsyncDNS must be running before being operated on."); 89 } 90 body { 91 return (cast(EventLoop)m_evLoop).resolveIP(url, 0, ipv6?isIPv6.yes:isIPv6.no); 92 } 93 94 // chooses a thread or starts it if necessary 95 private bool sendCommand() 96 in { assert(!waiting, "File is busy or closed"); } 97 body { 98 waiting = true; 99 m_error = false; 100 status = StatusInfo.init; 101 102 Waiter cmd_handler; 103 try { 104 cmd_handler = popWaiter(); 105 } catch (Throwable e) { 106 import std.stdio; 107 try { 108 status = StatusInfo(Status.ERROR, e.toString()); 109 m_error = true; 110 } catch {} 111 112 return false; 113 } 114 115 if (cmd_handler is Waiter.init) { 116 m_cmdInfo.addr = cast(shared)((cast()m_evLoop).resolveHost(cmdInfo.url, 0, cmdInfo.ipv6?isIPv6.yes:isIPv6.no)); 117 callback(); 118 return true; 119 } 120 assert(cmd_handler.cond); 121 m_cmdInfo.waiter = cast(shared)cmd_handler; 122 try { 123 synchronized(gs_wlock) 124 gs_jobs.insert(CommandInfo(CmdInfoType.DNS, cast(void*) this)); 125 126 cmd_handler.cond.notifyAll(); 127 } 128 catch (Exception e){ 129 import std.stdio; 130 try writeln("Exception occured notifying foreign thread: ", e); catch {} 131 } 132 return true; 133 } 134 135 /// Cleans up underlying resources. Used as a placeholder for possible future purposes. 136 bool kill() { 137 return true; 138 } 139 140 package: 141 synchronized @property DNSCmdInfo cmdInfo() { 142 return m_cmdInfo; 143 } 144 145 146 shared(NetworkAddress*) addr() { 147 try synchronized(m_cmdInfo.mtx) 148 return cast(shared)&m_cmdInfo.addr; 149 catch {} 150 return null; 151 } 152 153 synchronized @property void status(StatusInfo stat) { 154 m_status = cast(shared) stat; 155 } 156 157 synchronized @property bool waiting() const { 158 return cast(bool) m_busy; 159 } 160 161 synchronized @property void waiting(bool b) { 162 m_busy = cast(shared) b; 163 } 164 165 void callback() { 166 167 try { 168 m_handler(cast(NetworkAddress)m_cmdInfo.addr); 169 } 170 catch (Throwable e) { 171 static if (DEBUG) { 172 import std.stdio : writeln; 173 try writeln("Failed to send command. ", e.toString()); catch {} 174 } 175 } 176 } 177 178 } 179 180 package shared struct DNSCmdInfo 181 { 182 DNSCmd command; 183 bool ipv6; 184 string url; 185 NetworkAddress addr; 186 Waiter waiter; 187 AsyncSignal ready; 188 AsyncDNS dns; 189 Mutex mtx; // for NetworkAddress writing 190 } 191 192 package shared struct DNSReadyHandler { 193 AsyncDNS ctxt; 194 void delegate(NetworkAddress) del; 195 196 void opCall(NetworkAddress addr) { 197 assert(ctxt !is null); 198 del(addr); 199 return; 200 } 201 }