WebObjects/EOF/使用 EOF/上下文和資料庫鎖定
鎖定編輯上下文(共享編輯上下文或非共享)中的錯誤會導致您的例項死鎖。這是最棘手的難題之一。
您是否只需要在使用應用程式的無狀態部分(directActions)時才鎖定和解鎖編輯上下文,還是在狀態下也需要鎖定和解鎖?預設編輯上下文呢?它也需要鎖定和解鎖嗎?
如果您的會話只使用預設編輯上下文,那麼您不必擔心鎖定。
此外,手動鎖定/解鎖物件容易出錯。並且由此產生的錯誤最難修復,因為它們只會在高負載情況下隨機出現。我真的很覺得 WebObjects 應該使用 Java 同步來確保安全訪問,而無需手動編碼。但這可能意味著對 WebObjects 進行相當大的返工。
我以前也這麼想,但事實證明,這會引入很多開銷。您會遇到與原始 Java 集合類似的效能問題(很糟糕)。如果您能獲取一個鎖,然後在解鎖之前隨意遍歷 EC 和 EO API,那麼您確實節省了一些迴圈。我相信這裡還有更多開明的人可以詳細說明或糾正我;但這是我的印象。
順便說一句:我傾向於在會話級別建立額外的 EC,並在會話喚醒和休眠時進行鎖定和解鎖。請求處理程式防止一個請求在同一時間訪問會話;並且您可以保證會話喚醒和休眠每次請求迴圈只觸發一次。元件喚醒和休眠呼叫並非如此。如果您在元件級別進行鎖定/解鎖,那麼您死鎖的可能性更大。
事務在一個頁面上開始(鎖定),然後在操作後,您不返回該頁面,而是建立另一個頁面(可能再次鎖定,死鎖)。
我遇到的唯一棘手的事情是,如果您的應用程式使用長執行請求頁面。在這種情況下,從會話的角度來看,您會在一個邏輯“請求”上獲得多個請求/響應。也就是說,狀態頁面會不斷重新載入,但您的後臺作業會一直忙於執行其任務。如果它正在使用一個在會話喚醒/休眠時鎖定和解鎖的 EC,那麼在沒有投訴的情況下,您不會走得太遠。所以在這些情況下,如果使用者進入這樣的頁面並且正在使用一個 EC,我會停用該 EC 的自動會話鎖定/解鎖,直到作業完成。或者我建立一個專門用於後臺作業的 EC。
一般來說,您最好在會話中(如果您有一個)而不是元件中鎖定 EC。
由於這是通常的做法,因此有一些解決方案可以為您實現它。
注意:一個非常方便(如果我說得對自己的話)的用於鎖定您建立的 EC 的可重用解決方案(對於會話預設 EC 來說並不必要)可以在以下位置找到:http://WOCode.com/cgi-bin/WebObjects/WOCode.woa/wa/ShareCodeItem?itemId=301
需要注意的一點是,頁面上的 awake() 和 sleep() 中的 EC 鎖定並不真正起作用,因為 awake 可能比 sleep 被呼叫的次數更多。這意味著通常情況下您不能使用 DirectToWeb,因為它就是這麼做的。但是,ProjectWonder 有一個編輯上下文工廠,一個自動鎖定和解鎖的 EC 子類,以及一個在應用程式休眠時會釋放所有鎖定的鎖定管理器。
這與長響應頁面配合得很好,只要您不在長執行任務中使用會話的編輯上下文即可。
如果我在 DirectAction 中呼叫 session().defaultEditingContext(),那麼我目前正在防禦性地鎖定它。我需要這樣做嗎?或者它遵循與普通 WOComponent 中相同的(自動)鎖定規則?
您不需要鎖定它。
但是,無論如何,做類似
public WOActionResults someAction() {
WOActionResults page = pageWithName("Foo");
session().defaultEditingContext().lock;
try {
...
} finally {
session().defaultEditingContext().unlock;
}
return page;
}
的事情是不夠的,因為 appendToResponse 階段只在頁面返回後才發生,並且故障仍然可能在那裡觸發……所以當您在建立 EC 時使用它而不是使用會話 EC,您仍然必須在元件中鎖定它。
這是另一種處理方法
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 被鎖定且所有故障都處於安全狀態下被呼叫。
我一般避免在 direct actions 中使用會話的預設編輯上下文。呼叫 session().defaultEditingContext() 會建立一個會話,即使您實際上並不需要它。使用一個新的編輯上下文並根據需要鎖定它可能更高效,因為這不會導致不必要的會話被建立。
Project WOnder 的 ERXEC 為跟蹤 EOEditingContext 鎖定提供了廣泛的日誌記錄功能。如果您對使用 Project WOnder 不感興趣,那麼還有其他幾種方法。
您可以使用啟動引數顯示編輯上下文鎖定警告的堆疊跟蹤
-NSDebugLevel 2 -NSDebugGroups 18
這會為您提供一些資訊,但有一個更好的方法。您需要像這樣子類化 EOEditingContext
// 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() );
}
...
}