Come potrei evitare un deadlock distribuito durante una connessione reciproca tra due nodi?

11

Supponiamo di avere due nodi peer: il primo nodo può inviare una richiesta di connessione al secondo, ma anche il secondo può inviare una richiesta di connessione al primo. Come evitare una doppia connessione tra i due nodi? Per risolvere questo problema, sarebbe sufficiente rendere sequenziali le operazioni eseguite per la creazione di connessioni TCP in entrata o in uscita.

Ciò significa che ogni nodo deve elaborare in sequenza ogni nuova operazione di creazione della connessione, sia per le connessioni in entrata che per le connessioni in uscita. In questo modo, mantenendo un elenco di nodi connessi, prima di accettare una nuova connessione in entrata da un nodo o prima di inviare una richiesta di connessione a un nodo, sarà sufficiente verificare se questo nodo è già presente nell'elenco.

Per rendere sequenziali le operazioni di creazione delle connessioni, è sufficiente eseguire un blocco nell'elenco dei nodi connessi: infatti, per ogni nuova connessione, l'identificatore del nuovo nodo connesso è aggiunto a questa lista. Tuttavia, mi chiedo se questo approccio possa causare deadlock distribuito :

  • il primo nodo potrebbe inviare una richiesta di connessione al secondo;
  • il secondo nodo potrebbe inviare una richiesta di connessione al primo;
  • supponendo che le due richieste di connessione non siano asincrone, entrambi i nodi bloccano qualsiasi richiesta di connessione in entrata.

Come potrei risolvere questo problema?

UPDATE: Tuttavia, devo ancora bloccare l'elenco ogni volta che viene creata una nuova connessione (in entrata o in uscita), poiché altri thread potrebbero accedere a questo elenco, quindi il problema di deadlock sarebbe ancora rimangono.

UPDATE 2: Sulla base dei tuoi consigli, ho scritto un algoritmo per impedire l'accettazione reciproca di una richiesta di accesso. Poiché ogni nodo è un peer, potrebbe avere una routine client per inviare nuove richieste di connessione e una routine del server per accettare le connessioni in entrata.

ClientSideLoginRoutine() {
    for each (address in cache) {
        lock (neighbors_table) {
            if (neighbors_table.contains(address)) {
                // there is already a neighbor with the same address
                continue;
            }
            neighbors_table.add(address, status: CONNECTING);

        } // end lock

        // ...
        // The node tries to establish a TCP connection with the remote address
        // and perform the login procedure by sending its listening address (IP and port).
        boolean login_result = // ...
        // ...

        if (login_result)
            lock (neighbors_table)
                neighbors_table.add(address, status: CONNECTED);

    } // end for
}

ServerSideLoginRoutine(remoteListeningAddress) {
    // ...
    // initialization of data structures needed for communication (queues, etc)
    // ...

    lock(neighbors_table) {
        if(neighbors_table.contains(remoteAddress) && its status is CONNECTING) {
            // In this case, the client-side on the same node has already
            // initiated the procedure of logging in to the remote node.

            if (myListeningAddress < remoteListeningAddress) {
                refusesLogin();
                return;
            }
        }
        neighbors_table.add(remoteListeningAddress, status: CONNECTED);

    } // end lock
}

Esempio: L'IP: la porta del nodo A è A: 7001 - L'IP: la porta del nodo B è B: 8001.

Supponiamo che il nodo A abbia inviato una richiesta di accesso al nodo B: 8001. In questo caso, il nodo A chiama la routine di accesso inviando inviando il proprio indirizzo di ascolto (A: 7001). Di conseguenza, il neighbor_table del nodo A contiene l'indirizzo del nodo remoto (B: 8001): questo indirizzo è associato allo stato CONNECTING. Il nodo A è in attesa del nodo B accetta o rifiuta la richiesta di accesso.

Nel frattempo, anche il nodo B potrebbe aver inviato una richiesta di connessione all'indirizzo del nodo A (A: 7001), quindi il nodo A potrebbe elaborare la richiesta del nodo B. Quindi, il neighbor_table del nodo B contiene l'indirizzo del nodo remoto (A: 7001): questo indirizzo è associato allo stato CONNECTING. Il nodo B è in attesa del nodo A accetta o rifiuta la richiesta di accesso.

Se il lato server del nodo A rifiuta la richiesta da B: 8001, allora devo essere sicuro che il lato server del nodo B accetterà la richiesta da A: 7001. Allo stesso modo, se il lato server del nodo B rifiuta la richiesta da A: 7001, allora devo essere sicuro che il lato server del nodo A accetterà la richiesta da B: 8001.

In base alla regola "small address" , in questo caso il nodo A rifiuterà la richiesta di login il nodo B, mentre il nodo B accetterà la richiesta dal nodo A.

Che cosa ne pensi?

    
posta enzom83 24.08.2012 - 00:09
fonte

3 risposte

3

Puoi provare un approccio "ottimistico": connetti prima, quindi disconnetti se rilevi una connessione reciproca simultanea. In questo modo non è necessario mantenere le richieste di connessione mentre si stanno creando nuove connessioni: quando viene stabilita una connessione in entrata, bloccare l'elenco e verificare se si dispone di una connessione in uscita allo stesso host. Se lo fai, controlla l'indirizzo dell'ospite. Se è più piccolo del tuo, disconnetti la tua connessione in uscita; altrimenti, scollegare quello in entrata. Il tuo host peer farà l'opposto, perché gli indirizzi si confronteranno in modo diverso e una delle due connessioni verrà eliminata. Questo approccio ti consente di evitare di riprovare le connessioni e potenzialmente ti aiuta ad accettare più richieste di connessione per unità di tempo.

    
risposta data 24.08.2012 - 00:46
fonte
4

Quando un nodo invia una richiesta a un altro, potrebbe includere un numero intero a 64 bit casuale. Quando un nodo riceve una richiesta di connessione, se ne ha già inviata una propria, mantiene quella con il numero più alto e lascia cadere gli altri. Ad esempio:

Ora 1: A tenta di connettersi a B, invia il numero 123.

Tempo 2: B tenta di connettersi ad A, invia il numero 234.

Tempo 3: B riceve la richiesta di A. Poiché la richiesta di B ha un numero più alto, rifiuta la richiesta di A.

Tempo 4: A riceve la richiesta di B. Poiché la richiesta di B ha un numero più alto, A lo accetta e ne interrompe la richiesta.

Per generare il numero casuale, assicurati di seminare il generatore di numeri casuali con / dev / urandom, invece di usare il seeding predefinito del tuo generatore di numeri casuali, che è spesso basato sull'orario del wall clock: c'è una possibilità non ignorabile che due nodi otterranno lo stesso seme.

Invece di numeri casuali, potresti anche distribuire i numeri in anticipo (cioè solo numerare tutte le macchine da 1 a n), o usare un indirizzo MAC, o qualche altro modo di trovare un numero dove la probabilità di collisione è così piccola da essere ignorabile.

    
risposta data 24.08.2012 - 01:18
fonte
3

Se capisco, il problema che stai cercando di evitare è il seguente:

  • Node1 richiede la connessione dal nodo 2
  • Nodo1 blocca la lista di connessione
  • Node2 richiede la connessione dal nodo 1
  • Nodo2 blocca la lista di connessione
  • Nodo2 riceve la richiesta di connessione dal nodo1, rifiuta perché l'elenco è bloccato
  • Nodo1 riceve la richiesta di connessione dal nodo2, rifiuta perché l'elenco è bloccato
  • nessuno dei due finisce per connettersi l'un l'altro.

Posso pensare a un paio di modi diversi per affrontarlo.

  1. Se provi a connetterti a un nodo e rifiuta la tua richiesta con un messaggio "elenco bloccato", attendi un numero casuale di millisecondi prima di riprovare. (La casualità è critica: rende molto meno probabile che entrambi attenderanno esattamente lo stesso tempo e ripeteranno lo stesso problema ad infinitum .)
  2. Sincronizza gli orologi di entrambi i sistemi con un server orario e invia un timestamp insieme alla richiesta di connessione. Se una richiesta di connessione arriva da un nodo al quale stai attualmente cercando di connettersi, allora entrambi i nodi concordano che qualsiasi tentativo di connessione si è verificato per primo, e l'altra connessione viene chiusa.
risposta data 24.08.2012 - 00:30
fonte

Leggi altre domande sui tag