void XDebugProfiler::ensureBufferSpace() { if (m_nextFrameIdx < m_frameBufferSize) { return; } // The initial buffer size is 0 int64_t new_buf_size = (m_frameBufferSize == 0)? XDEBUG_GLOBAL(FramebufSize) : m_frameBufferSize * XDEBUG_GLOBAL(FramebufExpansion); try { int64_t new_buf_bytes = new_buf_size * sizeof(FrameData); m_frameBuffer = (FrameData*) smart_realloc((void*) m_frameBuffer, new_buf_bytes); m_frameBufferSize = new_buf_size; } catch (const OutOfMemoryException& e) { raise_error("Cannot allocate more memory for the xdebug profiler. Consider " "turning off profiling or tracing. Note that certain ini " "settings such as hhvm.xdebug.collect_memory and " "hhvm.xdebug.collect_time implicitly " "turn on tracing, so turn those off if this is unexpected.\n" "Current frame buffer length: %zu\n" "Failed to expand to length: %zu\n", m_frameBufferSize, new_buf_size); } }
// Starts tracing using the given profiler static void start_tracing(XDebugProfiler* profiler, folly::StringPiece filename = folly::StringPiece(), int64_t options = 0) { // Add ini settings if (XDEBUG_GLOBAL(TraceOptions)) { options |= k_XDEBUG_TRACE_APPEND; } if (XDEBUG_GLOBAL(TraceFormat) == 1) { options |= k_XDEBUG_TRACE_COMPUTERIZED; } if (XDEBUG_GLOBAL(TraceFormat) == 2) { options |= k_XDEBUG_TRACE_HTML; } // If no filename is passed, php5 xdebug stores in the default output // directory with the default file name. folly::StringPiece dirname; if (filename.empty()) { auto& default_dirname = XDEBUG_GLOBAL(TraceOutputDir); auto& default_filename = XDEBUG_GLOBAL(TraceOutputName); dirname = folly::StringPiece(default_dirname); filename = folly::StringPiece(default_filename); } auto const suffix = !(options & k_XDEBUG_TRACE_NAKED_FILENAME); auto abs_filename = format_filename(dirname, filename, suffix); profiler->enableTracing(abs_filename, options); }
void XDebugProfiler::collectFrameData(FrameData& frameData, const TypedValue* retVal) { VMRegAnchor _; // Ensure consistent state for vmfp and vmpc ActRec* fp = vmfp(); bool is_func_begin = retVal == nullptr; frameData.is_func_begin = is_func_begin; // The function reference and call file/line are stored when tracing/profiling // on function enter if ((m_tracingEnabled || m_profilingEnabled) && is_func_begin) { frameData.func = fp->func(); // Need the previous frame in order to get the call line. If we cannot // get the previous frame, default to 1 Offset offset; const ActRec* prevFp = g_context->getPrevVMState(fp, &offset); if (prevFp != nullptr) { frameData.line = prevFp->unit()->getLineNumber(offset); } else { frameData.line = 1; } } else { frameData.func = nullptr; frameData.line = 1; } // Time is stored if profiling, tracing, or collect_time is enabled, but it // only needs to be collected on function exit if profiling or if computerized // tracing output is enabled if (m_profilingEnabled || (is_func_begin && (m_collectTime || m_tracingEnabled)) || (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) { frameData.time = Timer::GetCurrentTimeMicros(); } else { frameData.time = 0; } // Memory usage is stored on function begin if tracing, or if collect_memory // is enabled, or on function end if computerized tracing output is enabled if ((is_func_begin && (m_tracingEnabled || m_collectMemory)) || (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) { frameData.memory_usage = MM().getStats().usage; } else { frameData.memory_usage = 0; } // If tracing is enabled, we may need to collect a serialized version of // the arguments or the return value. if (m_tracingEnabled && is_func_begin && XDEBUG_GLOBAL(CollectParams) > 0) { // TODO(#3704) This relies on xdebug_var_dump throw_not_implemented("Tracing with collect_params enabled"); } else if (m_tracingEnabled && !is_func_begin && XDEBUG_GLOBAL(CollectReturn)) { // TODO(#3704) This relies on xdebug_var_dump throw_not_implemented("Tracing with collect_return enabled"); } else { frameData.context_str = nullptr; } }
void XDebugHookHandler::onFlowBreak(const Unit* unit, int line) { if (XDEBUG_GLOBAL(Server) != nullptr) { // Translate the unit filepath and then break auto const filepath = String(const_cast<StringData*>(unit->filepath())); auto const transpath = File::TranslatePath(filepath); XDEBUG_GLOBAL(Server)->breakpoint(transpath, init_null(), init_null(), line); } }
void XDebugHookHandler::onFlowBreak(const Unit* unit, int line) { if (XDEBUG_GLOBAL(Server) != nullptr) { // Translate the unit filepath and then break const String filepath = String(unit->filepath()->data(), CopyString); const String transpath = File::TranslatePath(filepath); XDEBUG_GLOBAL(Server)->breakpoint(transpath, init_null(), init_null(), line); } }
void XDebugServer::attach(Mode mode) { assert(XDEBUG_GLOBAL(Server) == nullptr); try { XDEBUG_GLOBAL(Server) = new XDebugServer(mode); if (!XDEBUG_GLOBAL(Server)->initDbgp()) { detach(); } } catch (...) { // Fail silently, continue running the request. } }
bool XDebugServer::isNeeded() { if (!XDEBUG_GLOBAL(RemoteEnable) || XDEBUG_GLOBAL(RemoteMode) == "jit") { return false; } else if (XDEBUG_GLOBAL(RemoteAutostart)) { return true; } else { // Check $_COOKIE[XDEBUG_SESSION] const ArrayData* globals = get_global_variables()->asArrayData(); Array cookie = globals->get(s_COOKIE).toArray(); return !cookie[s_SESSION].isNull(); } }
void HHVM_FUNCTION( xdebug_var_dump, const Variant& v, const Array& _argv /* = null_array */ ) { if (!XDEBUG_GLOBAL(OverloadVarDump) || !XDEBUG_GLOBAL(DefaultEnable)) { HHVM_FN(var_dump)(v, _argv); return; } do_var_dump(v); auto const size = _argv.size(); for (int64_t i = 0; i < size; ++i) { do_var_dump(_argv[i]); } }
XDEBUG_NOTIMPLEMENTED static Variant HHVM_FUNCTION(xdebug_start_trace, const Variant& traceFileVar, int64_t options /* = 0 */) { // Allowed to pass null. folly::StringPiece trace_file; if (traceFileVar.isString()) { // We're not constructing a new String, we're just using the one in // traceFileVar, so this is safe. trace_file = traceFileVar.toString().slice(); } // Initialize the profiler if it isn't already. if (!XDEBUG_GLOBAL(ProfilerAttached)) { attach_xdebug_profiler(); } // php5 xdebug returns false when tracing already started. auto profiler = xdebug_profiler(); if (profiler->isTracing()) { return false; } // Start tracing, then grab the current begin frame start_tracing(profiler, trace_file, options); profiler->beginFrame(nullptr); return HHVM_FN(xdebug_get_tracefile_name)(); }
static void HHVM_FUNCTION(_xdebug_check_trigger_vars) { if (XDebugExtension::Enable && XDebugProfiler::isAttachNeeded() && !XDEBUG_GLOBAL(ProfilerAttached)) { attach_xdebug_profiler(); } }
// Detaches the xdebug profiler if it's no longer needed static void detach_xdebug_profiler_if_needed() { assert(XDEBUG_GLOBAL(ProfilerAttached)); auto profiler = xdebug_profiler(); if (!profiler->isNeeded()) { detach_xdebug_profiler(); } }
// Starts profiling using the given profiler static void start_profiling(XDebugProfiler* profiler) { // Add ini options int64_t opts = 0; if (XDEBUG_GLOBAL(ProfilerAppend)) { opts |= k_XDEBUG_PROFILE_APPEND; } // Create the filename then enable auto& dirname = XDEBUG_GLOBAL(ProfilerOutputDir); auto& filename = XDEBUG_GLOBAL(ProfilerOutputName); auto dirname_slice = folly::StringPiece{dirname}; auto filename_slice = folly::StringPiece{filename}; auto abs_filename = format_filename(dirname_slice, filename_slice, false); profiler->enableProfiling(abs_filename, opts); }
void XDebugServer::onRequestInit() { if (!XDEBUG_GLOBAL(RemoteEnable)) { return; } // Need to turn on debugging regardless of the remote mode in order to // capture exceptions/errors if (!DebugHookHandler::attach<XDebugHookHandler>()) { raise_warning("Could not attach xdebug remote debugger to the current " "thread. A debugger is already attached."); return; } // Grab $_GET, $_COOKIE, and the transport const ArrayData* globals = get_global_variables()->asArrayData(); Array get = globals->get(s_GET).toArray(); Array cookie = globals->get(s_COOKIE).toArray(); Transport* transport = g_context->getTransport(); // Need to check $_GET[XDEBUG_SESSION_STOP]. If set, delete the session // cookie const Variant sess_stop_var = get[s_SESSION_STOP]; if (!sess_stop_var.isNull()) { cookie.set(s_SESSION, init_null()); if (transport != nullptr) { transport->setCookie(s_SESSION, empty_string()); } } // Need to check $_GET[XDEBUG_SESSION_START]. If set, store the session // cookie with $_GET[XDEBUG_SESSION_START] as the value const Variant sess_start_var = get[s_SESSION_START]; if (sess_start_var.isString()) { String sess_start = sess_start_var.toString(); cookie.set(s_SESSION, sess_start); if (transport != nullptr) { int64_t expire = XDEBUG_GLOBAL(RemoteCookieExpireTime); if (expire > 0) { timespec ts; Timer::GetRealtimeTime(ts); expire += ts.tv_sec; } transport->setCookie(s_SESSION, sess_start, expire); } } }
static Variant HHVM_FUNCTION(xdebug_get_tracefile_name) { if (XDEBUG_GLOBAL(ProfilerAttached)) { auto profiler = xdebug_profiler(); if (profiler->isTracing()) { return profiler->getTracingFilename(); } } return false; }
void HHVM_FUNCTION(var_dump, const Variant& expression, const Array& _argv /*=null_array */) { #ifdef ENABLE_EXTENSION_XDEBUG if (UNLIKELY(XDEBUG_GLOBAL(OverloadVarDump) && XDEBUG_GLOBAL(DefaultEnable))) { HHVM_FN(xdebug_var_dump)(expression, _argv); return; } #endif VariableSerializer vs(VariableSerializer::Type::VarDump, 0, 2); do_var_dump(vs, expression); auto sz = _argv.size(); for (int i = 0; i < sz; i++) { do_var_dump(vs, _argv[i]); } }
// If the xdebug profiler is attached, ensures we still need it. If it is not // attached, check if we need it. static void refresh_xdebug_profiler() { if (XDEBUG_GLOBAL(ProfilerAttached)) { detach_xdebug_profiler_if_needed(); } else if (XDebugProfiler::isCollectionNeeded()) { // We know that the profiler is not attached, so either we failed the // request init checks or it was turned off during runtime. So we // only want to turn on profiling if collection is now needed. attach_xdebug_profiler(); } }
// Attempts to attach the xdebug profiler to the current thread. Assumes it // is not already attached. Raises an error on failure. static void attach_xdebug_profiler() { assert(!XDEBUG_GLOBAL(ProfilerAttached)); if (s_profiler_factory->start(ProfilerKind::XDebug, 0, false)) { XDEBUG_GLOBAL(ProfilerAttached) = true; // Enable profiling and tracing if we need to auto profiler = xdebug_profiler(); if (XDebugProfiler::isProfilingNeeded()) { start_profiling(profiler); } if (XDebugProfiler::isTracingNeeded()) { start_tracing(profiler); } profiler->setCollectMemory(XDEBUG_GLOBAL(CollectMemory)); profiler->setCollectTime(XDEBUG_GLOBAL(CollectTime)); profiler->setMaxNestingLevel(XDEBUG_GLOBAL(MaxNestingLevel)); } else { raise_error("Could not start xdebug profiler. Another profiler is " "likely already attached to this thread."); } }
void XDebugExtension::requestShutdown() { if (!Enable) { return; } s_xdebug_breakpoints.destroy(); // Potentially kill the profiler if (XDEBUG_GLOBAL(ProfilerAttached)) { detach_xdebug_profiler(); } }
void XDebugServer::onRequestInit() { if (!XDEBUG_GLOBAL(RemoteEnable)) { return; } // TODO(#4489053) Enable this when debugger internals have been refactored // Need to turn on debugging regardless of the remote mode in order to // capture exceptions/errors // ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck(); // ti->m_reqInjectionData.setDebugger(true); // Grab $_GET, $_COOKIE, and the transport const ArrayData* globals = get_global_variables()->asArrayData(); Array get = globals->get(s_GET).toArray(); Array cookie = globals->get(s_COOKIE).toArray(); Transport* transport = g_context->getTransport(); // Need to check $_GET[XDEBUG_SESSION_STOP]. If set, delete the session // cookie const Variant sess_stop_var = get[s_SESSION_STOP]; if (!sess_stop_var.isNull()) { cookie.set(s_SESSION, init_null()); if (transport != nullptr) { transport->setCookie(s_SESSION, empty_string()); } } // Need to check $_GET[XDEBUG_SESSION_START]. If set, store the session // cookie with $_GET[XDEBUG_SESSION_START] as the value const Variant sess_start_var = get[s_SESSION_START]; if (sess_start_var.isString()) { String sess_start = sess_start_var.toString(); cookie.set(s_SESSION, sess_start); if (transport != nullptr) { transport->setCookie(s_SESSION, sess_start, XDEBUG_GLOBAL(RemoteCookieExpireTime)); } } }
XDEBUG_NOTIMPLEMENTED Variant HHVM_FUNCTION(xdebug_get_profiler_filename) { if (!XDEBUG_GLOBAL(ProfilerAttached)) { return false; } auto profiler = xdebug_profiler(); if (profiler->isProfiling()) { return profiler->getProfilingFilename(); } return false; }
static void do_var_dump(const Variant& v) { auto const cli = XDEBUG_GLOBAL(CliColor); XDebugExporter exporter; exporter.max_depth = XDEBUG_GLOBAL(VarDisplayMaxDepth); exporter.max_children = XDEBUG_GLOBAL(VarDisplayMaxChildren); exporter.max_data = XDEBUG_GLOBAL(VarDisplayMaxData); exporter.page = 0; String str; auto const html_errors = ThreadInfo::s_threadInfo->m_reqInjectionData.hasHtmlErrors(); if (html_errors) { str = xdebug_get_zval_value_fancy(v, exporter); } else if ((cli == 1 && is_output_tty()) || cli == 2) { str = xdebug_get_zval_value_ansi(v, exporter); } else { str = xdebug_get_zval_value_text(v, exporter); } g_context->write(str); }
void XDebugHook::onBreak(const BreakInfo& bi) { // Have to have a server to break. if (XDEBUG_GLOBAL(Server) == nullptr) { return; } // Grab the breakpoints matching the passed info std::vector<int> ids; get_breakpoint_ids(bi, ids); // Iterate. Note that we only tell the server to break once. bool have_broken = false; for (auto const id : ids) { // Look up the breakpoint, ensure it's hittable. auto& bp = BREAKPOINT_MAP.at(id); if (!is_breakpoint_hit(bp)) { continue; } // We only break once per location. auto const temporary = bp.temporary; // breakpoint could be deleted if (!have_broken) { have_broken = true; // Grab the breakpoint message and do the break. auto const msg = get_breakpoint_message(bi); if (!XDEBUG_GLOBAL(Server)->breakpoint(bp, msg)) { // Kill the server if there's an error. XDebugServer::detach(); return; } } // Remove the breakpoint if it was temporary. if (temporary) { XDEBUG_REMOVE_BREAKPOINT(id); } } }
void XDebugHook::onOpcode(PC pc) { auto server = XDEBUG_GLOBAL(Server); if (server == nullptr) { return; } // Likely case is that there was no break command. auto brk = server->getAndClearBreak(); if (LIKELY(brk == nullptr)) { return; } server->log("Request thread received break command"); VMRegAnchor anchor; auto const unit = vmfp()->func()->unit(); auto const line = unit->getLineNumber(unit->offsetOf(pc)); auto const filepath = const_cast<StringData*>(unit->filepath()); auto const transpath = File::TranslatePath(String(filepath)); // XDebugServer::breakpoint will send the response for the command before the // break command, but we first need to send a response for the break command. auto response = xdebug_xml_node_init("response"); server->addXmlns(*response); auto const& cmd_str = brk->getCommandStr(); auto const& trans_id = brk->getTransactionId(); // Manually add status and reason. XDebugServer still thinks we're running // because we haven't run XDebugServer::breakpoint yet. xdebug_xml_add_attribute(response, "status", "break"); xdebug_xml_add_attribute(response, "reason", "ok"); // Ditto with command, XDebugServer is tracking the command before the break. xdebug_xml_add_attribute_dup(response, "command", cmd_str.data()); xdebug_xml_add_attribute_dup(response, "transaction_id", trans_id.data()); delete brk; server->sendMessage(*response); xdebug_xml_node_dtor(response); // Now we can go into a command loop. server->breakpoint(transpath, init_null(), init_null(), line); }
static bool HHVM_FUNCTION(xdebug_break) { auto server = XDEBUG_GLOBAL(Server); if (server == nullptr) { return false; } // Breakpoint displays the current file/line number auto file = g_context->getContainingFileName(); auto filename = file == nullptr ? empty_string() : String(file); auto const line = g_context->getLine(); // Attempt to perform the breakpoint, detach the server if something goes // wrong if (!server->breakpoint(filename, init_null(), init_null(), line)) { XDebugServer::detach(); } return true; }
XDEBUG_NOTIMPLEMENTED static Variant HHVM_FUNCTION(xdebug_stop_trace) { if (!XDEBUG_GLOBAL(ProfilerAttached)) { return false; } auto profiler = xdebug_profiler(); if (!profiler->isTracing()) { return false; } // End with xdebug_stop_trace() profiler->endFrame(init_null().asTypedValue(), nullptr, false); auto filename = profiler->getTracingFilename(); profiler->disableTracing(); detach_xdebug_profiler_if_needed(); return filename; }
void XDebugProfiler::writeTracingMemory(int64_t memory) { switch (outputType) { case TraceOutputType::NORMAL: fprintf(m_tracingFile, "%10lu ", memory); // Delta Memory (since the last begin frame) if (XDEBUG_GLOBAL(ShowMemDelta)) { if (m_tracingPrevBegin != nullptr) { int64_t prev_usage = m_tracingPrevBegin->memory_usage; fprintf(m_tracingFile, "%+8ld", memory - prev_usage); } else { fprintf(m_tracingFile, " "); } } break; case TraceOutputType::HTML: fprintf(m_tracingFile, "<td align='right'>%ld</td>", memory); break; case TraceOutputType::COMPUTERIZED: fprintf(m_tracingFile, "%lu\t", memory); break; } }
XDebugServer::XDebugServer(Mode mode) : m_mode(mode) { // Attempt to open optional log file if (XDEBUG_GLOBAL(RemoteLog).size() > 0) { m_logFile = fopen(XDEBUG_GLOBAL(RemoteLog).c_str(), "a"); if (m_logFile == nullptr) { raise_warning("XDebug could not open the remote debug file '%s'.", XDEBUG_GLOBAL(RemoteLog).c_str()); } log("Log opened at"); XDebugUtils::fprintTimestamp(m_logFile); log("\n"); logFlush(); } // Grab the hostname and port to connect to const char* hostname = XDEBUG_GLOBAL(RemoteHost).c_str(); int port = XDEBUG_GLOBAL(RemotePort); if (XDEBUG_GLOBAL(RemoteConnectBack)) { const ArrayData* globals = get_global_variables()->asArrayData(); Array server = globals->get(s_SERVER).toArray(); log("I: Checking remote connect back address.\n"); // Grab $_SERVER[HTTP_X_FORWARDED_FOR] then $_SERVER[REMOTE_ADDR] bool host_found = true; if (server[s_HTTP_X_FORWARDED_FOR].isString()) { hostname = server[s_HTTP_X_FORWARDED_FOR].toString().data(); } else if (server[s_REMOTE_ADDR].isString()) { hostname = server[s_REMOTE_ADDR].toString().data(); } else { host_found = false; } // Did we find a host? if (host_found) { log("I: Remote address found, connecting to %s:%d.\n", hostname, port); } else { log("W: Remote address not found, connecting to configured address/port: " "%s:%d. :-|\n", hostname, port); } } else { log("I: Connecting to configured address/port: %s:%d.\n", hostname, port); } // Create the socket m_socket = createSocket(hostname, port); if (m_socket == -1) { log("E: Could not connect to client. :-(\n"); goto failure; } else if (m_socket == -2) { log("E: Time-out connecting to client. :-(\n"); goto failure; } // Get the requested handler log("I: Connected to client. :-)\n"); if (XDEBUG_GLOBAL(RemoteHandler) != "dbgp") { log("E: The remote debug handler '%s' is not supported. :-(\n", XDEBUG_GLOBAL(RemoteHandler).c_str()); goto failure; } // php5 xdebug has an error case here, but initializing the dbgp handler never // actually fails initDbgp(); return; // Failure cleanup. A goto is used to prevent duplication failure: destroySocket(); closeLog(); // Allows the guarantee that any instance of an xdebug server is valid throw Exception("XDebug Server construction failed"); }
bool XDebugServer::initDbgp() { // Initialize the status and reason switch (m_mode) { case Mode::REQ: setStatus(Status::Starting, Reason::Ok); break; case Mode::JIT: setStatus(Status::Break, Reason::Error); break; } // Create the response auto response = xdebug_xml_node_init("init"); addXmlns(*response); // Add the engine info auto child = xdebug_xml_node_init("engine"); xdebug_xml_add_attribute(child, "version", XDEBUG_VERSION); xdebug_xml_add_text(child, XDEBUG_NAME, 0); xdebug_xml_add_child(response, child); // Add the author child = xdebug_xml_node_init("author"); xdebug_xml_add_text(child, XDEBUG_AUTHOR, 0); xdebug_xml_add_child(response, child); // Add the url child = xdebug_xml_node_init("url"); xdebug_xml_add_text(child, XDEBUG_URL, 0); xdebug_xml_add_child(response, child); // Add the copyright child = xdebug_xml_node_init("copyright"); xdebug_xml_add_text(child, XDEBUG_COPYRIGHT, 0); xdebug_xml_add_child(response, child); // Grab the absolute path of the script filename auto globals = get_global_variables()->asArrayData(); Variant scriptname_var = globals->get(s_SERVER).toArray()[s_SCRIPT_FILENAME]; assert(scriptname_var.isString()); auto scriptname = scriptname_var.toString().get()->mutableData(); auto fileuri = XDebugUtils::pathToUrl(scriptname); // Add attributes to the root init node xdebug_xml_add_attribute_ex(response, "fileuri", fileuri, 0, 1); xdebug_xml_add_attribute(response, "language", "PHP"); xdebug_xml_add_attribute(response, "protocol_version", DBGP_VERSION); xdebug_xml_add_attribute(response, "appid", getpid()); // Add the DBGP_COOKIE environment variable const String dbgp_cookie = g_context->getenv(s_DBGP_COOKIE); if (!dbgp_cookie.empty()) { xdebug_xml_add_attribute(response, "session", dbgp_cookie.data()); } // Add the idekey if (XDEBUG_GLOBAL(IdeKey).size() > 0) { xdebug_xml_add_attribute(response, "idekey", XDEBUG_GLOBAL(IdeKey).c_str()); } // Sent the response sendMessage(*response); xdebug_xml_node_dtor(response); // Wait for a response from the client return doCommandLoop(); }
void XDebugServer::detach() { assert(XDEBUG_GLOBAL(Server) != nullptr); XDEBUG_GLOBAL(Server)->deinitDbgp(); delete XDEBUG_GLOBAL(Server); XDEBUG_GLOBAL(Server) = nullptr; }
int XDebugServer::createSocket(const char* hostname, int port) { // Grab the address info sockaddr sa; sockaddr_in address; memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_port = htons((unsigned short) port); lookupHostname(hostname, address.sin_addr); // Create the socket auto const sockfd = socket(address.sin_family, SOCK_STREAM, 0); if (sockfd == -1) { log("create_debugger_socket(\"%s\", %d) socket: %s\n", hostname, port, folly::errnoStr(errno).c_str()); return -1; } // Put socket in non-blocking mode so we can use poll() for timeouts. fcntl(sockfd, F_SETFL, O_NONBLOCK); // Do the connect. auto const status = connect(sockfd, (sockaddr*)(&address), sizeof(address)); if (status < 0) { if (errno != EINPROGRESS) { return -1; } double timeout_sec = XDEBUG_GLOBAL(RemoteTimeout); // Loop until request has completed or there is an error. while (true) { pollfd pollfd; pollfd.fd = sockfd; pollfd.events = POLLIN | POLLOUT; pollfd.revents = 0; // Wait for a socket event. auto const status = poll(&pollfd, 1, (int)(timeout_sec * 1000.0)); // Timeout. if (status == 0) return -2; // Error. if (status == -1) return -1; // Error or hang-up. if ((pollfd.revents & POLLERR) || (pollfd.revents & POLLHUP)) return -1; // Success. if ((pollfd.revents & POLLIN) || (pollfd.revents & POLLOUT)) break; } // Ensure we're actually connected. socklen_t sa_size = sizeof(sa); if (getpeername(sockfd, &sa, &sa_size) == -1) { return -1; } } // Reset the socket to non-blocking mode and setup socket options fcntl(sockfd, F_SETFL, 0); long optval = 1; setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)); return sockfd; }