PJS를 이용해서 프로젝트를 진행하던중에 이상한 현상을 발견하게되었습니다.
게시판 페이징을 하면서 페이지번호 Element에 Event.observer를 할당하여 사용하고, Event가 필요없어지면 Event.stopObserving을 하는데, 여기서 메모리 누수가 발생하고 있었습니다.

일반적으로 생각하는건 아래와 같습니다.
1. 이벤트 설정 (Event.observe)
2. 이벤트 발생 (click...)
3. 이벤트 해지 (Event.stopObserving)
4. 이벤트 메모리 해지

하지만 4번이 제대로 되지 않는 현상이 발생하여 추적에 추적을 하였습니다(파이어버그 이용)
그랬더니 문제가 발생한 지점을 발견 이렇게 포스트하게 되었군요..ㅋㅋ

이벤트를 등록할때는 분명 _observeAndCache을 이용, observers 오브젝트에 이벤트관련 내용을 기록합니다. 허나, 이벤트를 삭제하는 stopObserving에서는 단순하게 이벤트를 삭제할뿐 observers 오브젝트에 기록한 이벤트 내용은 삭제하지 않고 계속 쌓아두고 오브젝트의 크기만을 늘리고 있었습니다.
물론 소스상에 unloadCache라는 것이 있어 observers 을 날려주고 있지만 이것을 기억하십시요.
unloadCache는 unload이벤트가 발생할때만 실행됩니다. 그러므로 Ajax를 통해 데이터를 받아오고 동적으로 이벤트를 Element에 설정했다면 unload이벤트는 발생하지도 않고 observers의 크기는 날로 커져만 갑니다. 이를 Prototype JS측에서는 알고 있는지 모르겠군요.

일단 제가 수정한 PrototypeJS의  소스는 아래와 같습니다.

  //Fix to prototype's  Event bug : Must decrease to Evnet.observers.length -> Memory leak
  //Added by blankus [www.blankus.net] 2007 - 07 - 13

  decreaseCache : function(elementId){
    if (!Event.observers) return;
 var tmp = [];
    for (var i = 0, length = Event.observers.length; i < length; i++) {
      //해당아이디의 이벤트를 삭제할때 observers의 내용에서도 해당아이디로 등록된 내용을 찾고,
      //만약 같은 내용이 있다면 그것만을 제외하고 새로운 오브젝트에 담아 새로운 Event.observers를 생성

      if(Event.observers[i][0]!=null && elementId != Event.observers[i][0].id) tmp.push(Event.observers[i]);
    }
 Event.observers = tmp;
  }

  stopObserving: function(element, name, observer, useCapture) {
    element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (Prototype.Browser.WebKit || element.attachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      try {
        element.detachEvent('on' + name, observer);
      } catch (e) {}
    }
 //Added by blankus 2007 - 07 - 13
 try{
  this.decreaseCache(element.id);
 }catch(e){}
  }

posted by blakus

Tip  |  2007/07/15 15:22
이 글의 트랙백 주소 :: http://www.blankus.net/trackback/20
.
PJS를 이용해서 프로젝트를 진행하던중에 이상한 현상을 발견하게되었습니다.게시판 페이징을 하면서 페이지번호 Element에 Event.observer를 할당하여 사용하고, Event가 필요없어지면 Event.stopObservi...
이벤트
2007/07/15 20:12 댓글에 댓글수정/삭제
좋내요. 많은 발전을 기대합니다. 지켜보고 있는 사람이 있음을 생각하세요.
2007/07/16 08:36 수정/삭제
이곳까지 와주시고 감사합니다 ^^ 열심히 하겠습니다 ㅎㅎ
.
2007/07/23 00:37 댓글에 댓글수정/삭제
오호~ 멋진 패치로군요. 개인적인 생각으로 동적인 observing은 안쓰는 것이 최선이라는 결론을 얻게 되었습니다. 말씀하신데로 무한으로 event가 생산되고 릭이 발생하기 때문이죠. 굳이 써야한다면 전역 또는 부모 element에 observe한개만 할당하고 반환되는 event를 분석(Event.element)하여 동적 event를 구현하는 것을 개인적으로 선호합니다.
.
DOCHI
2007/07/25 14:35 댓글에 댓글수정/삭제
덕분에 국내에서 볼 수 없는 좋은 소스를 보게 되네요.
(아직 초급이라 알아 볼 수는 없지만.. ^^;;)
2007/07/26 09:01 수정/삭제
찾아주셔서 감사합니다. 지금은 프로젝트 기간에 어느책의 베타리더로써 일을 하고있어서 포스트하는게 쉽지만은 않군요;; 하지만 열심히 노력하는 모습보이겠습니다. 자주 찾아주세요
.
2007/07/26 12:05 댓글에 댓글수정/삭제
이런 야로가.. 제대로 만들어야쥐 이거 프로타입에 올려야 되지 않을런지 ㅎㅎ
.
이벤트
2007/08/15 19:38 댓글에 댓글수정/삭제
하나의 이벤트 리스너를 해제할 때도 있지만, 목록(리스트)에 있는 이벤트 리스너를 전부 해제하는 경우, 한꺼번에 해제할 수 있었으면 좋겠습니다. 해제 대상 ID를 배열로 넘겨주고 이를 처리(arguments)하면 될 것 같기도 합니다.
.
2007/11/22 13:25 댓글에 댓글수정/삭제
이제 읽어보네요 정말 좋은 자료 감사합니다 ^^;

방법론(?) 혹은, 코딩스타일 차이겠지만
새로운 array(tmp)를 만드는 방법말고 배열 요소 한개를 삭제하는 방법도 있습니다
decreaseCache : function(~~){
for (~~~) {
if (~~~) return Event.observers.splice(i, 1); /*한개만찾고리턴*/
}
}

그리고, ID를 넘겨서 서치를 하셨는데 이는 문제의 여지가 있습니다
이런 경우가 생기면 안되겠지만 프로그램이 복잡해지고 각각 모듈을 다른 사람이 짜다보면 엘리멘트의 ID가 중복될 경우도 있습니다 이런 경우 문제가 발생할 수 있습니다.
ID 중복 문제를 ID 중복이라는 문제 자체의 문제라고쳐 제외하더라도, 더 치명적인 문제가 있습니다.
ID가 없는 경우입니다. 보통의 경우 $() 를 사용해 ID값으로 엘리멘트를 참조하긴합니다만, ID가 없이도 엘리멘트가 많이 쓰이고, 여러가지 방법으로 참조되어 사용되고 있기 때문입니다
아시겠지만 createElement 로 생성된 엘리멘트는 굳이 id 없이도 변수값으로 참조하고, 또 child나 parent와 같은 방식으로 그리고 tagName 등으로도 참조가능하지요

ps. 제가 착각해서 뭔가 중요한 것을 빼먹고 생각해서 지적을 함부로 잘못했을 가능성도 있습니다 :]
2007/11/22 14:01 수정/삭제
^^ 좋은 정보네요. ㅎㅎ 그리고 id를 넘겨 서치하는 것은 prototypeJS에서 이벤트 등록시 엘리먼트의 id로 핸들링이 되기때문에 전제조건이 있어 그렇습니다.(id는 중복되지 않는다는 조건하) 그리고 prototypeJS의 Event.observe 메소드를 살펴보시면 아시겠지만 내부적으로 $를 사용하여 엘리먼트를 제어하고 그 $의 인자가 string이면 document.getElementById를 수행합니다. ^^ 고로 id가 없는 문제는 다른 문제라고 생각합니다.
2007/11/22 16:00 수정/삭제
prototypeJS에서 보통의 경우 $()를 써서 element를 구한 다음 다시 이벤트를 등록할것이라고 예상을 하고 그렇게 편의상(?) 만든것인가 봅니다..

일단, 기본적으로 prototypeJS API에서 언급된 argument 타입이 "an actual DOM reference, or the ID string for the element."입니다.

사용자가 엘리멘트를 ID값으로만 참조해야 할 의무는 없는거죠. (앞서 말씀드렸듯 대부분(?)이 그렇다고해서 꼭 ID값이 있는 엘리멘트만 사용하는 것이 아닙니다, 특히 getElementsBy 로 시작하는 함수들이 꽤 있죠.)

일단, prototypeJS의 Event.observe 함수의 첫번째 인자값이 ID값이 있는 돔레퍼런스나 ID값만 받는 것이 아니니 ID값이 없는 엘리멘트를 제외한다는 것은 조금 틀렸다고 생각합니다 (줄이지 않아도 될 기능을 제한하는 것이니까요.)

그리고, 또 한가지 문제가 보이네요.
ID 값만으로 비교해서 배열을 제거한다면 문제가 생깁니다
한 엘리멘트의 한가지 이벤트만 거는 것은 아니지요
하지만 패치에선 그런 문제가 있네요..
제 생각에는 그냥 decreaseCache 함수가 ID값(string)만을 받기 보다는 [element, name, observer, useCapture] 를 모두 받아 모든 값이 일치하는 item을 한개만 삭제하는 것이 맞는거 같습니다

ps.사실 저는 DOM레퍼런스 값으로만 이벤트를 걸기 때문에 ID가 있는 엘리멘트만을 위한 패치라 조금 반발하고 있습니다 (게다가 ID 속성을 이벤트를 걸 엘리멘트의 필수적인 속성이라고 생각하지도 않고요.)
.
이름 ::   비밀번호 :: 홈페이지 :: 비밀글
등록