Frage

Ich habe eine Web-Anwendung, die steuert, welche Web-Anwendungen Datenverkehr von unserem Load Balancer serviert bekommen. Die Web-Anwendung läuft auf jedem einzelnen Server.

Es verfolgt den „in oder out“ Zustand für jede Anwendung in einem Objekt in dem ASP.NET-Anwendung Zustand, und das Objekt in eine Datei auf der Festplatte serialisiert, wenn der Zustand geändert wird. Der Staat wird aus der Datei deserialisiert, wenn die Web-Anwendung gestartet wird.

Während die Website selbst wird nur ein paar ein zweites Spitzen anfordert, und die Datei nur selten zugegriffen wird, habe ich festgestellt, dass es sehr einfach war, aus irgendeinem Grund Kollisionen zu bekommen, während Sie versuchen, zu lesen oder in die Datei schreiben. Dieser Mechanismus muss extrem zuverlässig sein, denn wir haben ein System automatisiert die regelmäßig Roll Installationen auf dem Server der Fall ist.

Bevor jemand macht keine Kommentare die Umsicht von einem der oben genannten Frage zu stellen, lassen Sie mich einfach sagen, dass die Argumentation erklärt dahinter würde diesen Beitrag viel mehr machen, als es ohnehin schon ist, so würde ich Berge versetzen vermeiden möge.

Wie gesagt, der Code, den ich verwende, um Zugriff auf die Datei sieht wie folgt aus steuern:

internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on 
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no 
/// locking collisions caused by attempting to save and load the file simultaneously 
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the 
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />. 
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
    var l = new Logger();
    if (ServerState._lock.WaitOne(1500, false))
    {
        l.LogInformation( "Got lock to read/write file-based server state."
                        , (Int32)VipEvent.GotStateLock);
        var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate 
                                  , FileAccess.ReadWrite, FileShare.None);                
        result = func.Invoke(fileStream);                
        fileStream.Close();
        fileStream.Dispose();
        fileStream = null;
        ServerState._lock.ReleaseMutex();
        l.LogInformation( "Released state file lock."
                        , (Int32)VipEvent.ReleasedStateLock);
        return true;
    }
    else
    {
        l.LogWarning( "Could not get a lock to access the file-based server state."
                    , (Int32)VipEvent.CouldNotGetStateLock);
        result = null;
        return false;
    }
}

Das in der Regel funktioniert, aber gelegentlich ich keinen Zugriff auf den Mutex bekommen (ich sehe das Ereignis im Protokoll „Es kann keine Sperre erhalten“). Ich kann dies nicht lokal reproduzieren - es geschieht nur auf meine Produktionsserver (Win Server 2k3 / IIS 6). Wenn ich das Timeout entfernen, die Anwendung hängt unbegrenzt (Race-Bedingung ??), auch auf nachfolgende Anfragen.

Wenn ich die Fehler zu tun bekommen, bei dem Ereignisprotokoll suchen sagt mir, dass die Mutex-Sperre wurde durch die vorherige Anforderung erreicht und freigegeben vor der Fehler protokolliert wurde.

Der Mutex wird in Ereignis Application_Start instanziiert. Ich erhalte die gleichen Ergebnisse, wenn es statisch in der Deklaration instanziiert wird.

Excuses, Ausreden. Threading / Verriegelung nicht meine Stärke ist, wie ich in der Regel muß darüber keine Sorge

Irgendwelche Vorschläge, warum sie nach dem Zufall scheitern würde ein Signal zu bekommen?


Update:

Ich habe Umgang mit richtigen Fehlern hinzugefügt (wie peinlich!), Aber ich bin immer noch die gleichen Fehler immer - und für die Aufzeichnung, nicht behandelte Ausnahmen waren nie das Problem

.

Nur ein Prozess würde jemals Zugriff auf die Datei - ich nicht mit einem Web-Garten für diese Web-Pool Anwendung und keine andere Anwendungen verwenden, um die Datei. Die einzige Ausnahme, die ich denken kann, wäre, wenn der App Pool recycelt, und die alten WP ist noch offen, wenn die neue erstellt wird - aber ich kann aus der Beobachtung der Task-Manager sagt, dass das Problem auftritt, während es nur ein Arbeitsprozess ist.

@mmr: Wie wird mit Überwachen anders von einem Mutex verwenden? Basierend auf der MSDN-Dokumentation, es sieht aus wie es effektiv das gleiche tun wird -. Wenn und ich kann nicht das Schloss mit meinem Mutex bekommen, es hat nicht ordnungsgemäß von nur falsch Rückkehr

Eine andere Sache zu beachten: Die Themen, das ich habe, scheinen völlig zufällig zu sein - wenn es auf eine Anforderung fehlschlägt, könnte es auf der nächsten gut funktionieren. Es scheint nicht zu einem Muster, entweder (sicherlich nicht jeder andere, zumindest) zu sein.


Update 2:

Diese Sperre wird für einen anderen Anruf nicht verwendet. Das einzige Mal, _lock außerhalb der InvokeOnFile referenzierten Methode ist, wenn es instanziiert wird.

Die Func die aufgerufen wird, wird entweder aus der Datei zu lesen und Deserialisierung in ein Objekt oder ein Objekt Serialisierung und ihn in die Datei zu schreiben. Weder Betrieb wird auf einem separaten Thread durchgeführt.

ServerState.PATH ist ein statischer Nur-Lese-Bereich, was ich erwarte keine Gleichzeitigkeit Probleme verursachen würde.

Ich würde auch gerne meinen früheren Punkt, erneut auf, dass ich diesen Ort nicht wiedergeben kann (in Cassini).


Was haben wir gelernt:

  • Verwenden Sie eine korrekte Fehlerbehandlung (duh!)
  • Verwenden Sie das richtige Werkzeug für den Job (und ein grundlegendes Verständnis von dem, was / wie das Werkzeug der Fall ist). Als sambo points aus, ein Mutex mit scheinbar viel Overhead hat, die Probleme in meiner Anwendung verursacht wurde, während Monitors speziell für .NET entwickelt wurde.
War es hilfreich?

Lösung

Sie sollten nur Mutexes verwenden, wenn Sie prozessübergreifende Synchronisation benötigen .

  

Obwohl ein Mutex kann verwendet werden für   Intra-Prozess-Thread-Synchronisation,   mit Monitor ist im Allgemeinen bevorzugt,   weil Monitore wurden entwickelt,   speziell für das .NET Framework   Gebrauch machen und deshalb eine bessere Nutzung der   Ressourcen. Im Gegensatz dazu die Mutex   Klasse ist ein Wrapper zu einem Win32   bauen. Während es ist mächtiger   als ein Monitor benötigt ein Mutex   interop Übergänge, die mehr   rechnerisch teuer als die   erforderlich durch die Monitor-Klasse.

Wenn Sie Inter-Prozess unterstützen müssen Sperren müssen Sie ein Globaler Mutex .

Das Muster verwendet wird, ist unglaublich zerbrechlich, gibt es keine Ausnahmebehandlung und sorgen Sie dafür, dass nicht Ihr Mutex freigegeben wird. Das ist wirklich riskant Code und wahrscheinlich der Grund, warum Sie diese hängt zu sehen, wenn es keine Zeitüberschreitung ist.

Auch, wenn Ihre Dateioperation je länger dauert als 1,5 Sekunden, dann gibt es eine Chance, gleichzeitig Mutexes wird es nicht in der Lage sein zu greifen. Ich würde empfehlen, die Verriegelung nach rechts und der Vermeidung der Timeout zu bekommen.

Ich denke, die beste dies neu zu schreiben, eine Sperre zu verwenden. Außerdem sieht es aus wie Sie auf eine andere Methode zu rufen, wenn dies für immer nehmen, wird die Sperre für immer. Das ist ziemlich riskant.

Dies ist sowohl kürzer und viel sicherer:

// if you want timeout support use 
// try{var success=Monitor.TryEnter(m_syncObj, 2000);}
// finally{Monitor.Exit(m_syncObj)}
lock(m_syncObj)
{
    l.LogInformation( "Got lock to read/write file-based server state."
                    , (Int32)VipEvent.GotStateLock);
    using (var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
                                     , FileAccess.ReadWrite, FileShare.None))
    {
        // the line below is risky, what will happen if the call to invoke
        // never returns? 
        result = func.Invoke(fileStream);
    }
}

l.LogInformation("Released state file lock.", (Int32)VipEvent.ReleasedStateLock);
return true;

// note exceptions may leak out of this method. either handle them here.
// or in the calling method. 
// For example the file access may fail of func.Invoke may fail

Andere Tipps

Wenn einige der Dateioperationen fehlschlagen, wird die Sperre nicht freigegeben werden. Wahrscheinlich ist dies der Fall. Legen Sie die Datei-Operationen in try / catch-Block, und die Sperre in dem finally-Block.

Wie auch immer, wenn Sie die Datei in Ihrer Global.asax Application_Start Methode lesen, dies, dass niemand sicher, sonst wird daran arbeitet (sagtest, dass die Datei beim Programmstart gelesen wird, nicht wahr?). Zur Vermeidung von Kollisionen auf Anwendungspool restaring usw. Sie können nur versuchen, die Datei zu lesen (unter der Annahme, dass der Schreibvorgang nimmt eine exklusive Sperre) und dann 1 Sekunde warten und erneut versuchen, wenn Ausnahme ausgelöst wird.

Jetzt haben Sie das Problem, die schreibt zu synchronisieren. Welches Verfahren auch immer entscheidet, um die Datei zu ändern sollte darauf achten, nicht einen Schreibvorgang beruft, wenn ein andere läuft mit einfacher lock-Anweisung ist.

Ich sehe ein paar mögliche Probleme hier.

Bearbeiten für Updates 2: Wenn die Funktion eine einfache serialize / deserialize Kombination ist, würde ich die beide aus in zwei verschiedenen Funktionen trennen, eine in eine ‚serialize‘ -Funktion und einen in eine ‚deserialize‘ Funktion. Sie sind wirklich zwei unterschiedliche Aufgaben. Sie können dann verschiedene, sperren spezifische Aufgaben haben. Invoke ist geschickt, aber ich habe in eine Menge Ärger mich für ‚netten‘ über ‚Arbeit‘.

gehen bekommen

1) Ist Ihre LogInformation Funktion Verriegelung? Weil Sie es im Inneren des Mutex nennen zuerst, und dann, wenn Sie den Mutex freigeben. Also, wenn eine Sperre gibt es in der Protokolldatei / Struktur zu schreiben, dann können Sie mit Ihrem Race-Bedingung dort landen. Um das zu vermeiden, setzen Sie das Protokoll innerhalb des Schlosses.

2) Schauen Sie sich die Monitor-Klasse verwenden, die ich Werke in C # wissen, und ich würde Werke in ASP.NET annehmen. Dafür können Sie einfach nur versuchen, die Sperre zu bekommen, und nicht anders anmutig. Eine Möglichkeit, dies zu verwenden, ist nur immer wieder versuchen, die Sperre zu erhalten. (Bearbeiten warum: siehe hier , im Grunde ein Mutex über Prozesse ist, ist der Monitor in nur einem Prozess, wurde aber für .NET entwickelt und so bevorzugt. Keine andere wirkliche Erklärung durch die docs gegeben ist.)

3) Was passiert, wenn die Filestream Öffnung ausfällt, weil jemand anderes die Sperre hat? Das wäre eine Ausnahme auslösen, und das könnte dieser Code verursachen schlecht verhalten (dh, wird die Sperre noch durch den Faden gehalten, der die Ausnahme hat, und ein anderer Thread kann es bekommen).

4) Was ist mit der Func selbst? Macht das einen weiteren Thread starten, oder ist es vollständig innerhalb des einen Thread? Was ist ServerState.PATH zugreifen?

5) Welche anderen Funktionen können ServerState._lock zugreifen? Ich ziehe jede Funktion zu haben, das eine Sperre benötigt eine eigene Sperre erhalten, Rasse / Deadlockbedingungen zu vermeiden. Wenn Sie viele viele Threads, und jeder von ihnen versuchen, auf dem gleichen Objekt zu sperren, sondern für ganz andere Aufgaben, dann könnte man mit Deadlocks und Rennen ohne wirklich leicht verständlichen Grund enden. Ich habe mit Code geändert, dass die Idee zu reflektieren, anstatt eine globale Sperre verwenden. (Ich weiß, schlagen andere Menschen eine globale Sperre, ich weiß nicht wirklich, dass die Idee gefällt, wegen der Möglichkeit, andere Dinge, die es für einige Aufgabe greifen, die nicht diese Aufgabe ist)

.
    Object MyLock = new Object();
    private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
    var l = null;
    var filestream = null;
    Boolean success = false;
    if (Monitor.TryEnter(MyLock, 1500))
        try {
            l = new Logger();
            l.LogInformation("Got lock to read/write file-based server state.", (Int32)VipEvent.GotStateLock);
            using (fileStream = File.Open(ServerState.PATH, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)){                
                result = func.Invoke(fileStream); 
            }    //'using' means avoiding the dispose/close requirements
            success = true;
         }
         catch {//your filestream access failed

            l.LogInformation("File access failed.", (Int32)VipEvent.ReleasedStateLock);
         } finally {
            l.LogInformation("About to released state file lock.", (Int32)VipEvent.ReleasedStateLock);
            Monitor.Exit(MyLock);//gets you out of the lock you've got
        }
    } else {
         result = null;
         //l.LogWarning("Could not get a lock to access the file-based server state.", (Int32)VipEvent.CouldNotGetStateLock);//if the lock doesn't show in the log, then it wasn't gotten; again, if your logger is locking, then you could have some issues here
    }
  return Success;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top