@@ -1826,13 +1826,250 @@ ucs_status_t ucp_ep_reconfig_clear_failed_lanes(ucp_ep_h ep,
18261826 return UCS_OK ;
18271827}
18281828
1829- /* Prepare failed lanes for recovery; return the subset to pack into
1830- * LANES_ADDR_REQUEST. */
1829+ /* Install an empty wireup proxy on the lane, replacing any failed-stub UCT
1830+ * EP left by a preceding ucp_ep_failover_reconfig(). The inner UCT EP is
1831+ * attached separately by ucp_ep_recovery_set_next_ep(). */
1832+ static ucs_status_t
1833+ ucp_ep_recovery_install_wireup_ep (ucp_ep_h ep , ucp_lane_index_t lane )
1834+ {
1835+ uct_ep_h old_uct_ep = ucp_ep_get_lane (ep , lane );
1836+ uct_ep_h wireup_ep_uct ;
1837+ ucs_status_t status ;
1838+
1839+ ucs_assert (old_uct_ep != NULL );
1840+ if (ucp_wireup_ep_test (old_uct_ep )) {
1841+ return UCS_OK ;
1842+ }
1843+
1844+ if (!ucp_is_uct_ep_failed (old_uct_ep )) {
1845+ /* The lane has been already recovered */
1846+ return UCS_ERR_NO_PROGRESS ;
1847+ }
1848+
1849+ status = ucp_wireup_ep_create (ep , & wireup_ep_uct );
1850+ if (status != UCS_OK ) {
1851+ return status ;
1852+ }
1853+
1854+ ucs_trace ("ep %p: recovery lane[%d] %p -> wireup_ep %p" , ep , lane ,
1855+ old_uct_ep , wireup_ep_uct );
1856+ ucp_ep_set_lane (ep , lane , wireup_ep_uct );
1857+ uct_ep_destroy (old_uct_ep );
1858+ return UCS_OK ;
1859+ }
1860+
1861+ /* Idempotently ensure the lane's wireup proxy has an inner transport EP. */
1862+ static ucs_status_t
1863+ ucp_ep_recovery_set_next_ep (ucp_ep_h ep , ucp_lane_index_t lane ,
1864+ const ucp_address_entry_t * ae )
1865+ {
1866+ const ucp_ep_config_key_t * cfg_key = & ucp_ep_config (ep )-> key ;
1867+ const ucp_rsc_index_t rsc_index = cfg_key -> lanes [lane ].rsc_index ;
1868+ const unsigned path_index = cfg_key -> lanes [lane ].path_index ;
1869+ uct_ep_h proxy = ucp_ep_get_lane (ep , lane );
1870+ ucp_wireup_ep_t * wireup_ep = ucp_wireup_ep (proxy );
1871+ uct_ep_h next_ep ;
1872+ ucs_status_t status ;
1873+
1874+ ucs_assertv (wireup_ep != NULL , "ep %p lane %d: expected wireup proxy" ,
1875+ ep , lane );
1876+
1877+ if (wireup_ep -> super .uct_ep != NULL ) {
1878+ return UCS_OK ;
1879+ }
1880+
1881+ if (ae == NULL ) {
1882+ ucs_assert (ucp_ep_is_lane_p2p (ep , lane ));
1883+ return ucp_wireup_ep_connect (proxy , 0 , rsc_index , path_index , 0 , NULL );
1884+ }
1885+
1886+ status = ucp_wireup_iface_ep_create (ucp_worker_iface (ep -> worker , rsc_index ),
1887+ ae , path_index , & next_ep );
1888+ if (status != UCS_OK ) {
1889+ return status ;
1890+ }
1891+
1892+ ucp_wireup_ep_set_next_ep (proxy , next_ep , rsc_index );
1893+ return UCS_OK ;
1894+ }
1895+
1896+ /**
1897+ * @brief Find a CONNECT_TO_IFACE address entry for the specified lane.
1898+ *
1899+ * Returns the reachable entry whose memory domain index and system device
1900+ * match the lane's recorded destination, to keep the rebuilt lane bound to
1901+ * the same peer device. Returns NULL if no such entry exists.
1902+ */
1903+ static const ucp_address_entry_t *
1904+ ucp_ep_recovery_find_iface_addr (ucp_ep_h ep , ucp_lane_index_t lane ,
1905+ const ucp_unpacked_address_t * remote_address )
1906+ {
1907+ const ucp_ep_config_key_lane_t * cfg_lane =
1908+ & ucp_ep_config (ep )-> key .lanes [lane ];
1909+ const ucp_rsc_index_t rsc_index = cfg_lane -> rsc_index ;
1910+ const ucp_address_entry_t * ae ;
1911+
1912+ ucp_unpacked_address_for_each (ae , remote_address ) {
1913+ if ((ae -> iface_addr != NULL ) &&
1914+ (ae -> md_index == cfg_lane -> dst_md_index ) &&
1915+ (ae -> sys_dev == cfg_lane -> dst_sys_dev ) &&
1916+ ucp_wireup_is_reachable (ep , 0 , rsc_index , ae , NULL , 0 )) {
1917+ return ae ;
1918+ }
1919+ }
1920+
1921+ return NULL ;
1922+ }
1923+
1924+ /* Rebuild one CONNECT_TO_IFACE lane: find peer iface addr -> install empty
1925+ * wireup proxy -> create fully-connected inner UCT EP and attach it -> mark
1926+ * ready. */
1927+ static ucs_status_t
1928+ ucp_ep_recovery_rebuild_iface_lane (
1929+ ucp_ep_h ep , ucp_lane_index_t lane ,
1930+ const ucp_unpacked_address_t * remote_address )
1931+ {
1932+ const ucp_address_entry_t * ae ;
1933+ ucs_status_t status ;
1934+
1935+ ae = ucp_ep_recovery_find_iface_addr (ep , lane , remote_address );
1936+ if (ae == NULL ) {
1937+ ucs_debug ("ep %p: no remote iface address for lane %d" , ep , lane );
1938+ return UCS_ERR_UNREACHABLE ;
1939+ }
1940+
1941+ status = ucp_ep_recovery_install_wireup_ep (ep , lane );
1942+ if (status != UCS_OK ) {
1943+ return status ;
1944+ }
1945+
1946+ status = ucp_ep_recovery_set_next_ep (ep , lane , ae );
1947+ if (status != UCS_OK ) {
1948+ ucs_diag ("ep %p: set_next_ep failed for recovery iface lane %d: %s" ,
1949+ ep , lane , ucs_status_string (status ));
1950+ return status ;
1951+ }
1952+
1953+ ucp_wireup_update_flags (ep , UCS_BIT (lane ),
1954+ UCP_WIREUP_EP_FLAG_READY |
1955+ UCP_WIREUP_EP_FLAG_REMOTE_CONNECTED );
1956+ ucs_debug ("ep %p: recovered iface-lane[%d] via rsc[%d]" , ep , lane ,
1957+ ucp_ep_get_rsc_index (ep , lane ));
1958+ return UCS_OK ;
1959+ }
1960+
1961+ /* Rebuild one p2p (CONNECT_TO_EP) lane. Flow: find peer ep_addr ->
1962+ * install empty wireup proxy -> ensure iface-only inner UCT EP ->
1963+ * connect_to_ep_v2 against peer ep_addr -> mark ready. */
1964+ static ucs_status_t
1965+ ucp_ep_recovery_rebuild_p2p_lane (
1966+ ucp_ep_h ep , ucp_lane_index_t lane ,
1967+ const ucp_unpacked_address_t * remote_address )
1968+ {
1969+ const ucp_address_entry_t * address_entry ;
1970+ const ucp_address_entry_ep_addr_t * ep_entry ;
1971+ ucp_lane_index_t remote_lane ;
1972+ ucs_status_t status ;
1973+
1974+ /* Symmetric assumption: the peer's lane index mirrors ours.
1975+ * ucp_address_pack() with lanes2remote==NULL stamps the sender-side local
1976+ * lane as the remote_lane tag in the ep_addr, so we look up by our own
1977+ * lane index. */
1978+ remote_lane = lane ;
1979+ status = ucp_wireup_find_remote_p2p_addr (ep , remote_lane , remote_address ,
1980+ & address_entry , & ep_entry );
1981+ if (status != UCS_OK ) {
1982+ ucs_debug ("ep %p: no remote ep_addr for p2p lane %d" , ep , lane );
1983+ return status ;
1984+ }
1985+
1986+ status = ucp_ep_recovery_install_wireup_ep (ep , lane );
1987+ if (status != UCS_OK ) {
1988+ return status ;
1989+ }
1990+
1991+ status = ucp_ep_recovery_set_next_ep (ep , lane , NULL );
1992+ if (status != UCS_OK ) {
1993+ return status ;
1994+ }
1995+
1996+ status = ucp_wireup_ep_connect_to_ep_v2 (ucp_ep_get_lane (ep , lane ),
1997+ address_entry , ep_entry );
1998+ if (status != UCS_OK ) {
1999+ ucs_diag ("ep %p: connect_to_ep_v2 failed for recovery p2p lane %d: %s" ,
2000+ ep , lane , ucs_status_string (status ));
2001+ return status ;
2002+ }
2003+
2004+ ucp_wireup_update_flags (ep , UCS_BIT (lane ),
2005+ UCP_WIREUP_EP_FLAG_READY |
2006+ UCP_WIREUP_EP_FLAG_REMOTE_CONNECTED );
2007+ ucs_debug ("ep %p: recovered p2p-lane[%d] via rsc[%d]" , ep , lane ,
2008+ ucp_ep_get_rsc_index (ep , lane ));
2009+ return UCS_OK ;
2010+ }
2011+
2012+ /* For each lane in `lanes_to_rebuild`, bring its UCT resources back online.
2013+ * Dispatches to the iface-connect or p2p helper based on lane type. Returns
2014+ * the bitmap of lanes successfully rebuilt; lanes not in the returned bitmap
2015+ * stay UCP_LANE_TYPE_FAILED and will be retried on a future recovery round. */
2016+ ucp_lane_map_t
2017+ ucp_ep_recovery_rebuild_lanes (ucp_ep_h ep , ucp_lane_map_t lanes_to_rebuild ,
2018+ const ucp_unpacked_address_t * remote_address )
2019+ {
2020+ ucp_lane_map_t rebuilt = 0 ;
2021+ ucp_lane_index_t lane ;
2022+ ucs_status_t status ;
2023+
2024+ ucs_for_each_bit (lane , lanes_to_rebuild ) {
2025+ if (ucp_ep_is_lane_p2p (ep , lane )) {
2026+ status = ucp_ep_recovery_rebuild_p2p_lane (ep , lane ,
2027+ remote_address );
2028+ } else {
2029+ status = ucp_ep_recovery_rebuild_iface_lane (ep , lane ,
2030+ remote_address );
2031+ }
2032+
2033+ if (status == UCS_OK ) {
2034+ rebuilt |= UCS_BIT (lane );
2035+ }
2036+ }
2037+
2038+ return rebuilt ;
2039+ }
2040+
2041+ /* Replace failed UCT stubs with wireup proxies for the given lane set and,
2042+ * for p2p lanes, create their iface-only inner transport EP so that the
2043+ * local ep_addr is available to ucp_address_pack() at REQUEST-send time.
2044+ * Returns the bitmap of lanes that ended up with the full proxy state
2045+ * required to appear in LANES_ADDR_REQUEST. Lanes that failed to reach
2046+ * that state are excluded so they don't get packed (which would try to
2047+ * read addresses from a half-built proxy). */
18312048static ucp_lane_map_t
18322049ucp_ep_recovery_prepare_lanes (ucp_ep_h ep , ucp_lane_map_t lanes )
18332050{
1834- /* TODO: lane rebuild is not implemented yet. */
1835- return 0 ;
2051+ ucp_lane_map_t lane_map = 0 ;
2052+ ucp_lane_index_t lane ;
2053+
2054+ ucs_for_each_bit (lane , lanes ) {
2055+ if (ucp_ep_recovery_install_wireup_ep (ep , lane ) != UCS_OK ) {
2056+ continue ;
2057+ }
2058+
2059+ /* For p2p lanes the ep_addr has to exist before we pack the
2060+ * REQUEST (ucp_address_pack iterates p2p_lanes and calls
2061+ * uct_ep_get_address on each). On failure the lane stays with an
2062+ * empty proxy and is skipped from this round; the next recovery
2063+ * round will retry. */
2064+ if (ucp_ep_is_lane_p2p (ep , lane ) &&
2065+ (ucp_ep_recovery_set_next_ep (ep , lane , NULL ) != UCS_OK )) {
2066+ continue ;
2067+ }
2068+
2069+ lane_map |= UCS_BIT (lane );
2070+ }
2071+
2072+ return lane_map ;
18362073}
18372074
18382075/* Send a LANES_ADDR_REQUEST for the currently failed lanes. */
@@ -1957,6 +2194,11 @@ int ucp_ep_recovery_progress(ucp_ep_h ep)
19572194
19582195 failed &= ~recovered ;
19592196 if (failed == 0 ) {
2197+ /* All failed lanes recovered - drop the retry state so a later
2198+ * round (failed == 0) does not trip the recovery_arg == NULL
2199+ * assertion. */
2200+ ucs_free (ep -> ext -> recovery_arg );
2201+ ep -> ext -> recovery_arg = NULL ;
19602202 goto done ;
19612203 }
19622204
0 commit comments