跳转到内容

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() );
    }
 
 ...
 }
华夏公益教科书