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() ); } ... }