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?