2012-12-28 2 views
3

나는 웹 스파이더 응용 프로그램을 모든 소스 코드에서 상속 받았다. 일반 팜플렛 스타일 웹 사이트 (15 페이지 미만)에서는 소프트웨어가 완벽하게 작동합니다.System.StackOverflowException LinqToHtml로 발생

(20,000 페이지 이상) 소프트웨어는 아래 코드에 표시된 줄에 StackOverflowException을 발생시킵니다.

재귀를 유용하게 사용하지 않는 것 같지만 불행히도 LinqToHtml (SuperStarCoders) 라이브러리가 사용되는 것은 지원되지 않습니다.

Partial Public Class Links 
    Public Property SiteUrl As String 
    Public Property SiteTitle As String 
    Public Property Site As String 
End Class 

다른 2 개 목록은 : 위에 LinkList 변수 (Typing.Links의)리스트가

Private Function ExportXml(Optional ByVal _Worker As ComponentModel.BackgroundWorker = Nothing) As Boolean 
    Dim _L = PopulateSEOList(_Worker) 
    Try 
     Dim _TmpStr As New Text.StringBuilder 
     Dim _X As New XDocument, _ct As Long = 0, _Elements As Typing.SEO.Elements = Nothing 
     ReportProgress(0, _Worker) 
     With _TmpStr 
      .Append("<?xml version=""1.0"" encoding=""UTF-8""?>") 
      .Append("<o7th.Web.Design.Web.Spider>") 
      For i As Long = 0 To _L.Count - 1 
       _ct += 1 
       .Append(" <Page>") 
       .Append("  <Link>" & XmlEscape(_L(i).Link) & "</Link>") 
       .Append("  <Title>" & XmlEscape(_L(i).Title) & "</Title>") 
       .Append("  <Keywords>" & XmlEscape(_L(i).Keywords) & "</Keywords>") 
       .Append("  <Description>" & XmlEscape(_L(i).Description) & "</Description>") 
       .Append("  <Elements>") 
       _Elements = _L(i).ContentElements 
       If _Elements IsNot Nothing Then 
        If _Elements.H1 IsNot Nothing Then 
         .Append(<H1> 
            <%= (From n In _Elements.H1.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H1>) 
        End If 
        If _Elements.H2 IsNot Nothing Then 
         .Append(<H2> 
            <%= (From n In _Elements.H2.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H2>) 
        End If 
        If _Elements.H3 IsNot Nothing Then 
         .Append(<H3> 
            <%= (From n In _Elements.H3.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H3>) 
        End If 
        If _Elements.H4 IsNot Nothing Then 
         .Append(<H4> 
            <%= (From n In _Elements.H4.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H4>) 
        End If 
        If _Elements.H5 IsNot Nothing Then 
         .Append(<H5> 
            <%= (From n In _Elements.H5.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H5>) 
        End If 
        If _Elements.H6 IsNot Nothing Then 
         .Append(<H6> 
            <%= (From n In _Elements.H6.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </H6>) 
        End If 
        If _Elements.UL IsNot Nothing Then 
         .Append(<UL> 
            <%= (From n In _Elements.UL.AsParallel() 
             Select 
             <Content><%= ConvertToCDATA(n) %></Content>).ToList() %> 
           </UL>) 
        End If 
        If _Elements.OL IsNot Nothing Then 
         .Append(<OL> 
            <%= (From n In _Elements.OL.AsParallel() 
             Select 
             <Content><%= ConvertToCDATA(n) %></Content>).ToList() %> 
           </OL>) 
        End If 
        If _Elements.STRONG IsNot Nothing Then 
         .Append(<STRONG> 
            <%= (From n In _Elements.STRONG.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </STRONG>) 
        End If 
        If _Elements.EM IsNot Nothing Then 
         .Append(<EM> 
            <%= (From n In _Elements.EM.AsParallel() 
             Select 
             <Content><%= XmlEscape(n) %></Content>).ToList() %> 
           </EM>) 
        End If 
        If _Elements.BLOCKQUOTE IsNot Nothing Then 
         .Append(<BLOCKQUOTE> 
            <%= (From n In _Elements.BLOCKQUOTE.AsParallel() 
             Select 
             <Content><%= ConvertToCDATA(n) %></Content>).ToList() %> 
           </BLOCKQUOTE>) 
        End If 
        If _Elements.A IsNot Nothing Then 
         .Append(<LINKS> 
            <%= (From n In _Elements.A.AsParallel() 
             Select 
             <Content> 
              <HREF><%= XmlEscape(n.Href) %></HREF> 
              <REL><%= XmlEscape(n.Rel) %></REL> 
              <TITLE><%= XmlEscape(n.Title) %></TITLE> 
              <TARGET><%= XmlEscape(n.Target) %></TARGET> 
              <CONTENT><%= XmlEscape(n.Content) %></CONTENT> 
             </Content>).ToList() %> 
           </LINKS>) 
        End If 
        If _Elements.IMG IsNot Nothing Then 
         .Append(<IMAGES> 
            <%= (From n In _Elements.IMG.AsParallel() 
             Select 
             <Content> 
              <SRC><%= XmlEscape(n.Source) %></SRC> 
              <ALT><%= XmlEscape(n.Alt) %></ALT> 
              <TITLE><%= XmlEscape(n.Title) %></TITLE> 
             </Content>).ToList() %> 
           </IMAGES>) 
        End If 
       End If 
       .Append("  </Elements>") 
       .Append("  <Content><![CDATA[" & _L(i).Content.ToString() & "]]></Content>") 
       .Append(" </Page>") 
       ReportProgress((_ct/_L.Count) * 100, _Worker) 
      Next 
      .Append("</o7th.Web.Design.Web.Spider>") 
     End With 
     Dim _xStr As String = _TmpStr.ToString() 
     _X = XDocument.Parse(_xStr) 
     _X.Save(ExportPath & "site.xml") 
     _X = Nothing 
     ReportProgress(100, _Worker) 
     Return True 
    Catch ex As Exception 
     'Put logging in here 
     Message = ex.Message & ":::Export.ExportXml" 
     Return False 
    End Try 
End Function 

: 여기

예외가 발생할 때 실행되는 코드이다 :

Imports Superstar.Html.Linq 

Public Class Typing 

Partial Public Class SEO 

    Public Property Link As String 
    Public Property Title As String 
    Public Property Description As String 
    Public Property Keywords As String 
    Public Property Content As HElement 
    Public Property ContentElements As Elements 

    Partial Public Class Elements 

     Public Property H1 As List(Of String) 
     Public Property H2 As List(Of String) 
     Public Property H3 As List(Of String) 
     Public Property H4 As List(Of String) 
     Public Property H5 As List(Of String) 
     Public Property H6 As List(Of String) 
     Public Property UL As List(Of String) 
     Public Property OL As List(Of String) 
     Public Property STRONG As List(Of String) 
     Public Property BLOCKQUOTE As List(Of String) 
     Public Property EM As List(Of String) 
     Public Property A As List(Of Links) 
     Public Property IMG As List(Of Images) 

     Partial Public Class Images 
      Public Property Source As String 
      Public Property Alt As String 
      Public Property Title As String 
     End Class 

     Partial Public Class Links 
      Public Property Href As String 
      Public Property Rel As String 
      Public Property Title As String 
      Public Property Target As String 
      Public Property Content As String 
     End Class 

    End Class 

End Class 

End Class 

ReportProgress는 이에 대한 Xaml 창의 백그라운드 작업자를보고하고 업데이트합니다 particual 상황이 진행 표시 줄 업데이트 :

Public Sub ReportProgress(ByVal ct As Integer, _Worker As ComponentModel.BackgroundWorker) 
    If _Worker IsNot Nothing Then 
     _Worker.ReportProgress(ct) 
     Threading.Thread.Sleep(500) 
    End If 
End Sub 

을하고 다운 클래스는 다음과 같습니다

Imports System.Reflection 
Imports System.Net 
Imports Superstar.Html.Linq 

Public Class Downloader 
Implements IDisposable 

''' <summary> 
''' Get the returned downloaded string 
''' </summary> 
''' <value></value> 
''' <returns></returns> 
''' <remarks></remarks> 
Public ReadOnly Property ReturnString As String 
    Get 
     Return _StrReturn 
    End Get 
End Property 
Private Property _StrReturn As String 

''' <summary> 
''' Get the returned downloaded byte array 
''' </summary> 
''' <value></value> 
''' <returns></returns> 
''' <remarks></remarks> 
Public ReadOnly Property ReturnBytes As Byte() 
    Get 
     Return _FSReturn 
    End Get 
End Property 
Private Property _FSReturn As Byte() 

Private Property _UserAgent As String = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13" 
Private Property DataReceived As Boolean = False 

''' <summary> 
''' Download a string, but do not block the calling thread 
''' </summary> 
''' <param name="_Path"></param> 
''' <remarks></remarks> 
Public Sub DownloadString(ByVal _Path As String, Optional ByVal _Worker As ComponentModel.BackgroundWorker = Nothing) 
    SetAllowUnsafeHeaderParsing20() 
    Using wc As New Net.WebClient() 
     With wc 
      Dim _ct As Long = 0 
      DataReceived = False 
      .Headers.Add("user-agent", _UserAgent) 
      .DownloadStringAsync(New System.Uri(_Path)) 
      AddHandler .DownloadStringCompleted, AddressOf StringDownloaded 
      Do While Not DataReceived 
       If _Worker IsNot Nothing Then 
        _ct += 1 
        ReportProgress(_ct, _Worker) 
       End If 
      Loop 
     End With 
    End Using 
End Sub 

''' <summary> 
''' Download a file, but do not block the calling thread 
''' </summary> 
''' <param name="_Path"></param> 
''' <remarks></remarks> 
Public Sub DownloadFile(ByVal _Path As String, Optional ByVal _Worker As ComponentModel.BackgroundWorker = Nothing) 
    SetAllowUnsafeHeaderParsing20() 
    Using wc As New Net.WebClient() 
     With wc 
      Dim _ct As Long = 0 
      DataReceived = False 
      .Headers.Add("user-agent", _UserAgent) 
      .DownloadDataAsync(New System.Uri(_Path)) 
      AddHandler .DownloadDataCompleted, AddressOf FileStreamDownload 
      Do While Not DataReceived 
       If _Worker IsNot Nothing Then 
        _ct += 1 
        ReportProgress(_ct, _Worker) 
       End If 
      Loop 
     End With 
    End Using 
End Sub 

''' <summary> 
''' Download a parsable HDocument, for using HtmlToLinq 
''' </summary> 
''' <param name="_Path"></param> 
''' <returns></returns> 
''' <remarks></remarks> 
Public Function DownloadHDoc(ByVal _Path As String, Optional ByVal _Worker As ComponentModel.BackgroundWorker = Nothing) As HDocument 
    Try 
     'StackOverFlowException Occurring Here! 
     DownloadString(_Path, _Worker) 
     Return HDocument.Parse(_StrReturn) 
    Catch soex As StackOverflowException 
     'put some logging in here, with the path attempted 
     Return Nothing 
    Catch ex As Exception 
     SetAllowUnsafeHeaderParsing20() 
     Return HDocument.Load(_Path) 
    End Try 
End Function 

#Region "Internals" 

Private Sub SetAllowUnsafeHeaderParsing20() 
    Dim a As New System.Net.Configuration.SettingsSection 
    Dim aNetAssembly As System.Reflection.Assembly = Assembly.GetAssembly(a.GetType) 
    Dim aSettingsType As Type = aNetAssembly.GetType("System.Net.Configuration.SettingsSectionInternal") 
    Dim args As Object() = Nothing 
    Dim anInstance As Object = aSettingsType.InvokeMember("Section", BindingFlags.Static Or BindingFlags.GetProperty Or BindingFlags.NonPublic, Nothing, Nothing, args) 
    Dim aUseUnsafeHeaderParsing As FieldInfo = aSettingsType.GetField("useUnsafeHeaderParsing", BindingFlags.NonPublic Or BindingFlags.Instance) 
    aUseUnsafeHeaderParsing.SetValue(anInstance, True) 
End Sub 

Private Sub FileStreamDownload(ByVal sender As Object, ByVal e As DownloadDataCompletedEventArgs) 
    If e.Cancelled = False AndAlso e.Error Is Nothing Then 
     DataReceived = True 
     _FSReturn = DirectCast(e.Result, Byte()) 
    Else 
     _FSReturn = Nothing 
    End If 
End Sub 

Private Sub StringDownloaded(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs) 
    If e.Cancelled = False AndAlso e.Error Is Nothing Then 
     DataReceived = True 
     _StrReturn = DirectCast(e.Result, String) 
    Else 
     _StrReturn = String.Empty 
    End If 
End Sub 

#End Region 

#Region "IDisposable Support" 
Private disposedValue As Boolean ' To detect redundant calls 

' IDisposable 
Protected Overridable Sub Dispose(disposing As Boolean) 
    If Not Me.disposedValue Then 
     If disposing Then 
     End If 
     _StrReturn = Nothing 
     _FSReturn = Nothing 
    End If 
    Me.disposedValue = True 
End Sub 

Public Sub Dispose() Implements IDisposable.Dispose 
    Dispose(True) 
    GC.SuppressFinalize(Me) 
End Sub 

#End Region 

End Class 

제가 위에서 말했듯이 happenning 어떤 재귀가처럼, 그것은 보이지 않는다. (적어도 내게 붙어있는 사람은 아무도 없다.) 그래서 나는 그것이 곧 HD 문서 안에 있다고 가정한다.

이것이 어디에서 잘못되었으며 문제를 해결하는 방법을 알려주시겠습니까?

나는 몇 가지 조사를 수행하고, 기본 스택 크기는 1메가바이트 것을 이해, 그래서이 진정으로 내가이 증가하는 시도해야하는 특별한 상황 중 하나입니다 궁금했다 ...

난 후 발견 추적을 여러 번 보았습니다. 특정 페이지에 도달했을 때 항상 발생했습니다. 이 페이지는 단지 크기가 500k를 초과합니다. 여기

는 호출 스택입니다 :

[External Code] 
>  o7th.Web.Design.Spider.Worker.dll!o7th.Web.Design.Spider.Worker.Downloader.DownloadHDoc(String _Path, System.ComponentModel.BackgroundWorker _Worker) Line 95 + 0x1e bytes Basic 
o7th.Web.Design.Spider.Worker.dll!o7th.Web.Design.Spider.Worker.Export.PopulateSEOList(System.ComponentModel.BackgroundWorker _Worker) Line 513 + 0x65 bytes Basic 
o7th.Web.Design.Spider.Worker.dll!o7th.Web.Design.Spider.Worker.Export.ExportXml(System.ComponentModel.BackgroundWorker _Worker) Line 70 + 0x1e bytes Basic 
o7th.Web.Design.Spider.Worker.dll!o7th.Web.Design.Spider.Worker.Export.RunExport(System.ComponentModel.BackgroundWorker _Worker) Line 30 + 0x17 bytes Basic 
o7th.Web.Design.WebSpider.exe!o7th.Web.Design.WebSpider.ParseLinks.RunExport(Object sender, System.ComponentModel.DoWorkEventArgs e) Line 106 + 0x2c bytes Basic 
[External Code] 

과 지역 주민이 내가 그 위에 언급 페이지의 크기가 50 만 이상 나를 보여줍니다

+0

추신 아마도 초기 목록을 정리할 때 여기에있는 시도를 볼 수 있습니다 (문제의 일부라고 생각 함) – Kevin

+0

또 다른 메모. 그것을하는 페이지 수는 아닙니다. 추적을 여러 번보고 난 후에 특정 페이지를 공격했을 때 항상 발생했다고 생각했습니다. 이 페이지는 단지 크기가 500k를 초과합니다. – Kevin

+0

어쨌든 코드에 결함이있는 것 같습니다. 너무 많은 병렬 처리 (imho)를 사용하는 것 외에도, 여러 작업/스레드는 (대부분) 스레드 안전하지 않은 _doc.Descendants를 반복합니다. 그리고 그런 경우에는 StackOverflow가 결과일까요? – igrimpe

답변

2

(그렇지 않으면 공간이 더 필요했습니다. @Jakub Konecki의 게시물에 댓글을 달았습니다.)

나는 지난 몇 년 동안 여러 개의 스파이더를 만들었으며 병렬 처리에 대한 유일한 큰 성능 향상은 실제로 URL을 다운로드하는 것입니다. 대형 문서에서 수백 밀리 초의 HTML 파싱을 면도 할 수 있지만 디버깅 가격만큼 이득을 얻지는 못합니다. 그래서 인생을 더 편하게하고 평행법을 제거하십시오.

이상한 비동기 차단 문제가 있습니다. DownloadHDoc에서는 DownloadString을 동 기적으로 호출하지만 DownloadString의 내부에서는 비동기 메소드를 시작한 다음 비트 플래그를 차단하여 비동기의 목적을 무력화합니다. 더 나쁜 것은 시간당 백만 마일을 회전하고 매회 ReportProgress을 호출하는 do-while 루프를 차단하고 있다는 것입니다. 이것이 실제로 당신에게 SOE를주는 것입니다. Thread.Sleep(100)을 넣어두면 처음부터 도움이 될 것입니다.

는 [EDIT]

비트 플래그 차단 코드는이 인 :

 .DownloadStringAsync(New System.Uri(_Path)) 
     AddHandler .DownloadStringCompleted, AddressOf StringDownloaded 
     Do While Not DataReceived 
      If _Worker IsNot Nothing Then 
       _ct += 1 
       ReportProgress(_ct, _Worker) 
      End If 
     Loop 

라인 비동기 방식 끄 차기는 라인 (2)는 완료 및 반환을위한 핸들러를 추가 바로. 3 행은 전역 변수를 계속 확인하여 함수 StringDownloaded이이를 설정하기를 기다리고 있습니다. 이것은 매초 또는 수천 번 이상 발생합니다. 최적이 아니지만, 나쁜 점은 매번 ReportProgress 메서드를 호출한다는 것입니다. 문서가 커질수록 ReportProgress으로 전화가 걸립니다. 당신은 단지 100ms마다 UI를 업데이트 할 필요가 있습니다. 보통 250ms 또는 500ms마다 제 설정을합니다.

[수정 2]

위는 같은으로 변경 할 수 있어야 문제라면 :

.DownloadStringAsync(New System.Uri(_Path)) 
    AddHandler .DownloadStringCompleted, AddressOf StringDownloaded 
    Do While Not DataReceived 
     If _Worker IsNot Nothing Then 
      _ct += 1 
      ReportProgress(_ct, _Worker) 
     End If 
     Thread.Sleep(250) ''//Sleep inside of the loop 
    Loop 
+0

위의 모든 설명을 읽으십시오. 병렬 처리를 제거했습니다. :) 또한 설명하십시오. 당신이 뜻하는 바를 모르겠다. 또한'ReportProgress'는 당초 초기에 정확한 Sleep 값을 가졌습니다. 불필요하다고 생각해서 제거 했어요. 내가 다시 그것을 추가하고 그것을 밖으로 시도하고 무슨 일이 일어나는가 보자 – Kevin

+0

는'Thread.Sleep (100)'에서 다시 추가하려고 시도하고 숫자로 비트를 연주했지만, 같은 SOE, 같은 호출 스택, 같은 지역 주민 – Kevin

+0

so 나는 궁금해. 'do-while' 루프 위의'Thread.Sleep (100)'에 추가하면 더 좋을 것입니다 ... – Kevin

0

나는 모든 병렬 처리를 제거하여 시작할 것 - 어쨌든 과도한에 아마 여러 스레드를 작성하는 오버 헤드가 성능 향상보다 큽니다.

일단 코드를 디버그하고 예외가 발생할 때까지 기다리십시오. 콜 스택 및 모든 콜렉션을 확인할 수 없습니다.

일반적으로 스택 오버플로는 재귀 적으로 같은 메서드를 호출하면 어떤 이유로 종료 조건이 실행되지 않습니다. 호출 스택에 명확하게 표시됩니다.

+0

나는 그 결과를 보았다. 내 질문을 보아라. 나는 그것을 편집했고, 어디에서, 그리고 어떤 페이지에서 이것이 일어나고 있는지 발견했다. 한 번, 나는 목록에서 구문 분석 할 페이지를 제외하고 예외가 발생하지 않습니다. – Kevin

+0

그래서 콜 스택이 무엇입니까? –

+0

분 안에 게시됩니다 ... 현재 다른 테스트를 실행 중 – Kevin

관련 문제