跳轉到內容

WebObjects/EOF/使用 EOF/上下文和資料庫鎖定

來自華夏公益教科書,開放書籍,開放世界

鎖定您的編輯上下文(共享編輯上下文或非共享上下文)中的錯誤會導致您的例項死鎖。這是最令人頭疼的問題之一。

您是否只需要在使用應用程式的無狀態部分(DirectActions)時鎖定和解鎖編輯上下文,還是在有狀態的情況下也需要鎖定和解鎖?預設的編輯上下文呢,它是否也需要鎖定和解鎖?

如果您的會話只使用預設的編輯上下文,您就不需要擔心鎖定。

此外,手動鎖定/解鎖物件很容易出錯。並且由此產生的錯誤最難修復,因為它們只會在高負載情況下隨機發生。我真的覺得 WebObjects 應該使用 Java 同步來確保安全訪問,而不需要手動編碼。但這可能意味著 WebObjects 的重大重構。

我曾經也這樣想過,但事實證明,這樣做會引入很多開銷。您將面臨一個類似於原始 Java 集合(很差)的效能情況。如果您能獲取一個鎖,然後隨意遍歷 EC 和 EO API 直到解鎖,那麼您確實可以節省一些迴圈。我確信這裡還有其他人比我更有見識,可以詳細說明或糾正我;但這是我的印象。

順便說一下:我傾向於在會話級別建立額外的 EC,並在會話的 awake 和 sleep 中進行鎖定和解鎖。請求處理程式防止多個請求同時訪問會話;並且您保證會話的 awake 和 sleep 在每個請求迴圈中只觸發一次。這對於元件的 awake 和 sleep 呼叫來說是不正確的。如果您在元件級別進行鎖定/解鎖,您就有更大的死鎖可能性。

事務在一個頁面上開始(鎖定),然後在操作後,您不返回該頁面,而是建立另一個頁面(可能再次鎖定,死鎖)。

我遇到的唯一棘手的事情是,如果您的應用程式使用長時間執行的請求頁面。在這種情況下,您將獲得多個請求/響應,這些請求/響應被會話視為一個邏輯上的“請求”。也就是說,狀態頁面將重新載入,但您的後臺作業正在持續忙碌地執行其操作。如果它忙於使用一個 EC,該 EC 在會話的 awake/sleep 中鎖定和解鎖,那麼您將無法在沒有投訴的情況下走得太遠。因此,在這些情況下,如果使用者進入這樣的頁面並且正在使用 EC,我會停用該 EC 的自動會話鎖定/解鎖,直到作業完成。或者,我建立一個專門用於後臺作業的 EC。

David LeBer

[編輯 | 編輯原始碼]

一般來說,您最好在會話中(如果您有一個會話)而不是在元件中鎖定 EC。

由於這是通常的做法,有一些解決方案可以為您實現它。

Jonathan Rochkind

[編輯 | 編輯原始碼]

注意:一個非常方便(如果我敢說的話)的用於鎖定您建立的 EC 的可重用解決方案(對於會話預設 EC 不需要)可以在以下位置找到:http://WOCode.com/cgi-bin/WebObjects/WOCode.woa/wa/ShareCodeItem?itemId=301

Anjo Krank

[編輯 | 編輯原始碼]

需要注意的是,在頁面的 awake() 和 sleep() 上的 EC 鎖定實際上不起作用,因為 awake 可能會比 sleep 更頻繁地被呼叫。這意味著您通常不能使用 DirectToWeb,因為它就是這樣做的。但是,ProjectWonder 有一個編輯上下文工廠、一個自動鎖定和解鎖的 EC 子類以及一個鎖定管理器,它會在應用程式休眠時釋放所有鎖。

這對長時間響應頁面來說很有效,前提是您不在長時間執行的任務中使用會話的編輯上下文。

在 DirectAction 中鎖定

[編輯 | 編輯原始碼]

如果我在 DirectAction 中呼叫 session().defaultEditingContext(),我目前正在防禦性地鎖定它。我需要這樣做嗎,還是它遵循與普通 WOComponent 中相同的(自動)鎖定規則?

Anjo Krank

[編輯 | 編輯原始碼]

您不需要鎖定它。

但無論如何,像

 public WOActionResults someAction() {
   WOActionResults page = pageWithName("Foo");
   session().defaultEditingContext().lock;
   try {
  ...
   } finally {
       session().defaultEditingContext().unlock;
   }
   return page;
 }

這樣的操作是不夠的,因為 appendToResponse 階段只發生在頁面返回 *之後*,並且仍然可能在那裡觸發錯誤……因此,當您在那裡建立 EC 而不是使用會話 EC 時,您仍然必須在元件中鎖定它。

Chuck Hill

[編輯 | 編輯原始碼]

以下是處理此問題的另一種方法。

 public WOActionResults someAction() {
   WOComponent page = pageWithName("Foo");
   WOResponse response;
   session().defaultEditingContext().lock;
   try {
  ...
       response = page.generateResponse().
   } finally {
       session().defaultEditingContext().unlock;
   }
   return response;
 }

對 generateResponse 的呼叫在解鎖之前確保 appendToResponse 在 EC 被鎖定時被呼叫,並且所有錯誤都在安全狀態下被觸發。

Robert Walker

[編輯 | 編輯原始碼]

我一般避免在 DirectAction 中使用會話的預設編輯上下文。呼叫 session().defaultEditingContext() 會建立一個會話,即使您實際上並不需要。使用新的編輯上下文並根據需要鎖定它可能更有效,因為這不會導致不必要的會話建立。

跟蹤 EOEditingContext 鎖

[編輯 | 編輯原始碼]

Project WOnder 的 ERXEC 為跟蹤 EOEditingContext 鎖提供了廣泛的日誌記錄功能。如果您不想使用 Project WOnder,還有其他幾種方法。

您可以使用啟動引數顯示編輯上下文鎖定警告的堆疊跟蹤

 -NSDebugLevel 2 -NSDebugGroups 18

這會給您一些資訊,但有一個更好的方法。您需要像這樣子類化 EOEditingContext

LockErrorScreamerEditingContext

[編輯 | 編輯原始碼]
 // LockErrorScreamerEditingContext.java
 //
 // Copyright (c) 2002-2003 Red Shed Software. All rights reserved.
 // by Jonathan 'Wolf' Rentzsch (jon at redshed dot net)
 // enhanced by Anthony Ingraldi (a.m.ingraldi at larc.nasa.gov)
 //
 // Thu Mar 28 2002 wolf: Created.
 // Thu Apr 04 2002 wolf: Made NSRecursiveLock-aware by Anthony.
 // Thu Jun 22 2003 wolf: Made finalizer-aware. Thanks to Chuck Hill.
 
 import com.webobjects.eocontrol.*;
 import com.webobjects.foundation.*;
 import java.io.StringWriter;
 import java.io.PrintWriter; 
 
 public class LockErrorScreamerEditingContext extends EOEditingContext {
    private String _nameOfLockingThread = null;
    private NSMutableArray _stackTraces = new NSMutableArray();
 
    public LockErrorScreamerEditingContext() {
        super();
    }
 
    public LockErrorScreamerEditingContext(EOObjectStore parent) {
        super(parent);
    }
 
    public void lock() {
        String nameOfCurrentThread = Thread.currentThread().getName();
        if (_stackTraces.count() == 0) {
            _stackTraces.addObject(_trace());
            _nameOfLockingThread = nameOfCurrentThread;
            //NSLog.err.appendln("+++ Lock number (" +  _stackTraces.count() + ") in " + nameOfCurrentThread);
        } else {
            if (nameOfCurrentThread.equals(_nameOfLockingThread)) {
                _stackTraces.addObject(_trace());
                //NSLog.err.appendln("+++ Lock number (" + _stackTraces.count() + ") in " + nameOfCurrentThread);
            } else {
                NSLog.err.appendln("!!! Attempting to lock editing context from " + nameOfCurrentThread
                                   + " that was previously locked in " + _nameOfLockingThread);
                NSLog.err.appendln("!!! Current stack trace: \n" + _trace());
                NSLog.err.appendln("!!! Stack trace for most recent lock: \n" + _stackTraces.lastObject());
            }
        }
        super.lock();
    }
 
    public void unlock() {
        super.unlock();
        if (_stackTraces.count() > 0)
            _stackTraces.removeLastObject();
        if (_stackTraces.count() == 0)
            _nameOfLockingThread = null;
        String nameOfCurrentThread = Thread.currentThread().getName();
        //NSLog.err.appendln("--- Unlocked in " +  nameOfCurrentThread + " (" + _stackTraces.count() + " remaining)");
    }
    
    public void goodbye() {
        if (_stackTraces.count() != 0) {
            NSLog.err.appendln("!!! editing context being disposed with " + _stackTraces.count() + " locks.");
            NSLog.err.appendln("!!! Most recently locked by: \n"
                               + _stackTraces.lastObject());
        }
    }
 
    public void dispose() {
        goodbye();
        super.dispose();
    }
     
    protected void finalize() throws Throwable {
       try {
           goodbye();
       } finally {
           super.finalize();
       }
    }
 
    private String _trace() {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        (new Throwable()).printStackTrace(printWriter);
        return stringWriter.toString();
    }
 }

現在您必須在建立 EOEditingContext 時替換子類。不幸的是,WOF 中沒有通用的 EOEditingContext 工廠方法,但這將涵蓋預設的編輯上下文。

 public class Session extends WOSession {
    public Session() {
            super();
            setDefaultEditingContext( new LockErrorScreamerEditingContext() );
    }
 
    public Session( String sessionID ) {
            super( sessionID );
            setDefaultEditingContext( new LockErrorScreamerEditingContext() );
    }
 
 ...
 }
華夏公益教科書