Sicherheit der Verwendung von Thread.current [] in Schienen

81

Ich bekomme immer wieder widersprüchliche Meinungen über die Praxis des Speicherns von Informationen im Thread.currentHash (z. B. current_user, aktuelle Subdomain usw.). Die Technik wurde vorgeschlagen, um die spätere Verarbeitung innerhalb der Modellschicht (Abfrageumfang, Prüfung usw.) zu vereinfachen.

Viele halten die Praxis für inakzeptabel, da sie das MVC-Muster bricht. Andere äußern Bedenken hinsichtlich der Zuverlässigkeit / Sicherheit des Ansatzes, und meine zweiteilige Frage konzentriert sich auf den letzteren Aspekt.

  1. Ist der Thread.currentHash während seines gesamten Zyklus garantiert für eine und nur eine Antwort verfügbar und privat?

  2. Ich verstehe, dass ein Thread am Ende einer Antwort durchaus an andere eingehende Anfragen übergeben werden kann, wodurch alle darin gespeicherten Informationen verloren gehen Thread.current. Würde das Löschen solcher Informationen vor dem Ende der Antwort (z. B. durch Ausführen Thread.current[:user] = nilvon einem Controller after_filter) ausreichen, um eine solche Sicherheitsverletzung zu verhindern?

Vielen Dank! Giuseppe

Giuseppe
quelle
9
Lesen Sie hier den Abschnitt "Mit Thread.current schmutzig werden". m.onkey.org/thread-safety-for-your-rails . Das wurde von einem der Jruby-Autoren geschrieben. Der ROR-Code Nr. 1 selbst verwendet Thread.current für I18N und time_zone. Spricht das von seiner Garantie? # 2. Wenn # 1 wahr ist, ist es ausreichend.
so_mv
Danke, ich habe die Referenz hinzugefügt.
Giuseppe
3
In dem Beitrag, auf den Sie verlinken, befassen sich die Beispiele ausschließlich mit der Controller-Schicht, und die vorgeschlagene Lösung ist offensichtlich angemessen. Ich vermute jedoch, dass die meisten Menschen an einer sauberen Möglichkeit interessiert wären, den Zugriff auf Modelle auf 1-2 Informationen zu ermöglichen, die normalerweise für sie ausgeschlossen sind, ohne dass jedem Aufruf von Modellen zusätzliche Parameter hinzugefügt werden. In dieser Hinsicht haben mich all diese großen, beängstigenden Warnschilder "von Thread.current fernhalten" ohne bestimmte Gründe, warum sie mich bisher unsicher gemacht haben. Danke
Giuseppe

Antworten:

48

Es gibt keinen bestimmten Grund, sich von threadlokalen Variablen fernzuhalten. Die Hauptprobleme sind:

  • Es ist schwieriger, sie zu testen, da Sie daran denken müssen, die threadlokalen Variablen festzulegen, wenn Sie Code testen, der sie verwendet
  • Klassen, die Thread-Locals verwenden, benötigen das Wissen, dass diese Objekte nicht für sie verfügbar sind , sondern sich in einer thread-lokalen Variablen befinden. Diese Art der Indirektion verstößt normalerweise gegen das Demeter-Gesetz
  • Das Nichtbereinigen von Thread-Locals kann ein Problem sein, wenn Ihr Framework Threads wiederverwendet (die thread-lokale Variable wurde bereits initiiert und Code, der auf || = Aufrufen zum Initialisieren von Variablen beruht, schlägt möglicherweise fehl

Die Verwendung ist zwar nicht völlig ausgeschlossen, aber der beste Ansatz besteht darin, sie nicht zu verwenden. Von Zeit zu Zeit stoßen Sie jedoch an eine Wand, an der ein lokaler Thread die einfachste Lösung darstellt, ohne viel Code und zu ändern Sie müssen Kompromisse eingehen, ein weniger als perfektes objektorientiertes Modell mit dem lokalen Thread haben oder eine Menge Code ändern, um dasselbe zu tun.

Es ist also meistens eine Frage des Denkens, welche die beste Lösung für Ihren Fall sein wird, und wenn Sie wirklich den thread-lokalen Pfad beschreiten, würde ich Ihnen sicherlich raten, dies mit Blöcken zu tun, die daran denken, danach aufzuräumen Sie werden wie folgt ausgeführt:

around_filter :do_with_current_user

def do_with_current_user
    Thread.current[:current_user] = self.current_user
    begin
        yield
    ensure
        Thread.current[:current_user] = nil
    end      
end

Dadurch wird sichergestellt, dass die lokale Thread-Variable vor der Verwendung bereinigt wird, wenn dieser Thread recycelt wird.

Maurício Linhares
quelle
1
Die Verwendung eines Rundfilters mit einem Sicherstellungsblock beeinträchtigt die Ausnahmebehandlung / -protokollierung, wenn Sie nicht sehr vorsichtig sind. Schauen Sie sich RequestStore in Dejans Antwort an, um eine Middleware-basierte, sauberere Lösung zu erhalten.
Irongaze.com
Möglicherweise war meine Herangehensweise an das Problem darin, dass ich zuerst die Ausnahme rettete (anstatt nur wie oben sicherzustellen). Der Aufrufstapel der Ausnahme wurde an den Ort zurückgesetzt, an dem ich ihn erneut ausgelöst habe, wodurch das Rails-Fehlerprotokoll und andere Bereiche durcheinander gebracht wurden. Am Ende habe ich die erfasste Ausnahme manuell in die Protokolldatei geschrieben, um sie beizubehalten, aber sie war hässlich. Die Verwendung eines Middleware-basierten Ansatzes ist meiner Meinung nach viel sauberer.
Irongaze.com
1
Aus diesem Grund hat der obige Code nur einen ensureBlock und versucht nicht, die Ausnahme abzufangen.
Maurício Linhares
Einfach, wenn Sie es sehen :)
244an
4

Die akzeptierte Antwort ist technisch korrekt, aber wie in der Antwort sanft und in http://m.onkey.org/thread-safety-for-your-rails nicht so sanft ausgeführt:

Verwenden Thread.currentSie keinen lokalen Thread-Speicher, wenn Sie dies nicht unbedingt müssen

Das Juwel für request_storeist eine andere Lösung (besser), aber lesen Sie die Readme-Datei dort aus weiteren Gründen, um sich vom lokalen Thread-Speicher fernzuhalten.

Es gibt fast immer einen besseren Weg.

Tom Harrison
quelle
3
Ich verwende Unicorn mit Rails Multi-Tenant-App. Können Sie ein Beispiel für einen "besseren Weg" vorschlagen? Der von Ihnen gepostete Link löst einen Anwendungsfehler aus. Vielen Dank.
Lacostenycoder