2012-09-24 1 views
4

Node.js의 내부 동작에 익숙하지 않지만 너무 많은 함수 호출을 할 때 '최대 호출 스택 크기 초과'오류가 발생한다는 것을 알고 있습니다.Node.js에서 많은 데이터가 스택 크기를 초과 할 수 있습니까?

나는 링크를 따라갈 거미를 만들고있어 크롤링 된 URL의 무작위 수 이후에 이러한 오류가 발생하기 시작했습니다. 이 경우 노드는 스택 추적을주지 않지만 재귀 오류가없는 것은 확실합니다.

내가 URL을 가져 request을 사용하고

나는 이 인출 된 HTML을 분석하고 새로운 링크를 감지 cheerio를 사용하여했다. 스택 오버플로는 항상 cheerio 내부에서 발생합니다. htmlparser2에 대한 cheerio를 바꿀 때 오류가 사라졌습니다. Htmlparser2는 전체 문서를 파싱하고 트리를 구성하는 대신 열려있는 각 태그에 이벤트를 방출하기 때문에 훨씬 가볍습니다.

내 이론은 cheerio가 스택의 모든 메모리를 먹었지만 이것이 가능할 지 확신하지 못합니다.

여기 내 코드의 단순화 된 버전입니다 (단지 읽기 위해, 그것은 실행되지 않습니다이다) : 당신이 거기에가는 몇 가지 재귀를 가지고 같은

var _  = require('underscore'); 
var fs  = require('fs'); 
var urllib = require('url'); 
var request = require('request'); 
var cheerio = require('cheerio'); 

var mongo = "This is a global connection to mongodb."; 
var maxConc = 7; 

var crawler = { 
    concurrent: 0, 
    queue:  [], 
    fetched: {}, 

    fetch: function(url) { 
    var self = this; 

    self.concurrent += 1; 
    self.fetched[url] = 0; 

    request.get(url, { timeout: 10000, pool: { maxSockets: maxConc } }, function(err, response, body){ 
     self.concurrent -= 1; 
     self.fetched[url] = 1; 
     self.extract(url, body); 
    }); 
    }, 

    extract: function(referrer, data) { 
    var self = this; 
    var urls = []; 

    mongo.pages.insert({ _id: referrer, html: data, time: +(new Date) }); 

    /** 
    * THE ERROR HAPPENS HERE, AFTER A RANDOM NUMBER OF FETCHED PAGES 
    **/ 
    cheerio.load(data)('a').each(function(){ 
     var href = resolve(this.attribs.href, referer); // resolves relative urls, not important 

     // Save the href only if it hasn't been fetched, it's not already in the queue and it's not already on this page 
     if(href && !_.has(self.fetched, href) && !_.contains(self.queue, href) && !_.contains(urls, href)) 
     urls.push(href); 
    }); 

    // Check the database to see if we already visited some urls. 
    mongo.pages.find({ _id: { $in: urls } }, { _id: 1 }).toArray(function(err, results){ 
     if(err) results = []; 
     else results = _.pluck(results, '_id'); 

     urls = urls.filter(function(url){ return !_.contains(results, url); }); 
     self.push(urls); 
    }); 
    }, 

    push: function(urls) { 
    Array.prototype.push.apply(this.queue, urls); 
    var url, self = this; 

    while((url = self.queue.shift()) && this.concurrent < maxConc) { 
     self.fetch(url); 
    } 
    } 

}; 

crawler.fetch('http://some.test.url.com/'); 
+0

나는 치어로 같은 오류가 발생했습니다. 당신이 원인을 알아 냈습니까? – Lloyd

+0

불행히도 없습니다. 프로젝트의 경우 htmlparser2 만 사용하면 충분했으며 오류는 발생하지 않았습니다. – disc0dancer

+0

좋아 .. 결국 수동으로 html 텍스트를 조작해야만 cheerio에 전달하기 전에 파싱했는데 걱정하지 않은 모든 마크 업을 제거했다. – Lloyd

답변

0

보인다. 재귀 함수 호출은 함수 포인터가 저장되는 곳이기 때문에 결국 스택을 초과합니다.

  1. 푸시 호출이 while 루프 내부에 가져 호출
  2. 추출 호출이 mongo.pages.find 콜백에 밀어 request.get 콜백에서 추출 가져 오기 : 그런 일이 어떻게 그래서 여기

    입니다

이주기는 스택이 부족할 때까지 반복되는 것으로 보입니다.

cheerio.load으로 전화를 걸면 스택이 매우 낮아서 바로 실행됩니다.

당신이 가장 가능성이 버그 또는 뭔가 의도가있는 경우 바로 재귀를 사용하지 않고 nodejs에서 같은 효과를 얻기 위해, 검사 할 만

은 사용하는 것입니다

process.nextTick(functionToCall).

캡쳐 된 함수는 스택에서 꺼내지 만 다음 틱에서는 ​​functionToCall을 호출합니다.

당신은 noderepl에서 그것을 시도 할 수 있습니다 :

process.nextTick(function() { console.log('hello'); })

바로 '안녕하세요'인쇄됩니다.

setTimeout(functionToCall, 0)과 비슷하지만 그보다 더 바람직합니다.

코드와 관련하여 self.fetch(url)process.nextTick(function() { self.fetch(url); })으로 대체 할 수 있으며 더 이상 스택이 부족하지 않아야합니다.

위에서 언급했듯이 코드에 버그가있을 확률이 높으므로 먼저 살펴보십시오.

+0

나는 재귀가 너처럼 진행되고 있다고 생각하지 않는다. 설명했다. reqest.get은 process.nextTick()에 실제 요청을 래핑합니다. 또한, extract()와 push()는 프로세스의 다음 틱에서 발생하는 콜백 내에서 호출됩니다. 예를 들어, fetch를 호출하면, 더 이상의 재귀를 수행하지 않는 request.get() 만 호출됩니다. – disc0dancer

+0

좋은 점은 스택을 다 썼을 가능성이 가장 높기 때문에 재귀가 의심 스럽습니다. 그러나 이러한 콜백이 실제로 비동기 (즉, 즉시 콜백하지 않음)이면 올바르게 작동하고 재귀가 스택이 부족한 이유는 아닙니다. –

0

self.concurrent -= 1;을 너무 일찍 줄이면 모든 비동기 작업이 완료된 후 extract 함수 내에서 감소해야합니다. 그것은 하나의 장애물입니다. 비록 그것이 그것을 해결할 것 인 지 명확히하지 않고있는.

관련 문제