We have constructed a windows service unde W2003 Server in vb.net 2010 to process certain database transactions. It should work 24/7 o the server. However, after 3/4 days it blocks the windows server operation since the memory usage is ever-increasing. After many revisions, we decided to place under "comments" all operational code, including all transactions to the SQl Server database and left just a kind of shell service that is doing no real work. Still, under these circumstances, the memory usage goes on increasing. Any idea how to prevent this from happening? Thanks. Orlando

This can happen for many reasons.

The most commons are:
* the creation of objects that never are disposed. IE: Inside a never ending loop, SomeThing = new WhatItWas
* Infinite recursivity
* Lots of pending transactions in memory, big tables or collections, etc.

If you post your code, we can try to analyze where the problem is.

Hope this helps

Lola, thanks very much for your prompt reply. Here's the code. Some wording is in Spanish as we are located in Costa Rica and the code is for a local customer.

Orlando

[#Region "Métodos"
Public Function AbreNuevaConexion() As Boolean
Try
moConexion = New clsConexion(Me.HileraConexion)
With moConexion
AddHandler .MensajeGenerado, AddressOf MyBase.ReportaMensaje
Call .Abre()]
End With
Return True
Catch ex As Exception
Call MyBase.ReportaMensaje("Abriendo conexión con la base de datos.", ex)
Return False
End Try
End Function
Public Function CambiaEstadoTransaccion(ByVal anCodigoCaja As Integer _
, ByVal anCodigoTransaccion As Integer) As Boolean
With New Datos.clsTransaccion
.Conexion = moConexion
Return .CambiaEstado(anCodigoCaja, anCodigoTransaccion, .zEstadoGenerado)
End With
End Function
Public Sub Procesa()
Dim lConexionAbierta As Boolean = False
Dim nProcesadas As Integer = 0
Dim oEmisor As New clsEmisor
Dim oTransaccion As New Datos.clsTransaccion()
Dim oDatosTransaccion As New Datos.clsDatosTransaccion()
Dim oMovimientoFuncionario As New Datos.clsMovimientoFuncionario
Dim oFactura As New clsFactura()
Dim oTablaTransacciones As DataTable = Nothing
Dim oFilaEmisor As DataRow = Nothing
Dim oFilaDatosTransaccion As DataRow = Nothing
Dim oFilaExoneracion As DataRow = Nothing
Try
If Me.Trazo Then
Call MyBase.ReportaMensaje(Me.MemoriaEnUso() & " Inicio de proceso ...")
End If
If Not Me.AbreNuevaConexion() Then
GoTo EtiSalida
End If
lConexionAbierta = True
With oEmisor
.Conexion = moConexion
oFilaEmisor = .Datos()
End With
' Obtiene la lista de transacciones pendientes
With oTransaccion
.Conexion = moConexion
oTablaTransacciones = .ListaPendientes(Me.TransaccionesBloque)
If oTablaTransacciones.Rows.Count <= 0 Then
GoTo EtiSalida
End If
End With
oMovimientoFuncionario.Conexion = moConexion
AddHandler oFactura.MensajeGenerado, AddressOf MyBase.ReportaMensaje
With oDatosTransaccion
.Conexion = moConexion
Dim nCodigoCaja As Short
Dim nCodigoTransaccion As Integer
For Each oFilaTransaccion As DataRow In oTablaTransacciones.Rows
' Obtiene la llave de la transacción
nCodigoCaja = oFilaTransaccion(Datos.clsTransaccion.zAtrCodigoCaja)
nCodigoTransaccion = oFilaTransaccion(Datos.clsTransaccion.zAtrCodigoTransaccion)
oFilaDatosTransaccion = Nothing
oFilaDatosTransaccion = .DatosTransaccion(nCodigoCaja, nCodigoTransaccion)
If Not IsNothing(oFilaDatosTransaccion) Then
Dim nTotalExonerado As Double = oFilaDatosTransaccion(.zAtrMontoExonerado)
Dim lEstaExonerado As Boolean = (nTotalExonerado > 0)
' Constuye el código de la factura correspondiente con la transacción
Call oFactura.ConstruyeCodigo(oFilaEmisor(clsEmisor.zCodSucursal) _
, nCodigoCaja, nCodigoTransaccion)
If lEstaExonerado Then
oFilaExoneracion = Nothing
oFilaExoneracion = oMovimientoFuncionario.DatosFactura(oFactura.Codigo)
' Si no encuentra la datos de la exoneración, verifica si el tiempo ya ha expirado
If IsNothing(oFilaExoneracion) Then
Dim dFechaTransaccion As Date = oFilaDatosTransaccion(.zAtrFechaTransaccion)
Dim nTiempoEspera As Integer = Me.TiempoEsperaExoneracion()
If Me.SegundosDesde(dFechaTransaccion) <= nTiempoEspera Then
' Si el tiempo no ha expirado, deja la transacción para procesamiento posterior
Continue For
End If
End If
End If
If Not Me.ProcesaTransaccion(oFilaEmisor, oFilaDatosTransaccion, oDatosTransaccion, oFactura _
, nTotalExonerado, lEstaExonerado, oFilaExoneracion) Then
GoTo EtiSalida
End If
End If
nProcesadas += 1
Next
End With
Catch ex As Exception
Call MyBase.ReportaMensaje("Procesando transacción.", ex)
End Try
EtiSalida:
oEmisor = Nothing
oTransaccion = Nothing
oDatosTransaccion = Nothing
oMovimientoFuncionario = Nothing
oFactura = Nothing
Call Datos.clsTransaccion.LiberaTabla(oTablaTransacciones)
If Not IsNothing(oFilaEmisor) Then
Datos.clsTransaccion.LiberaTabla(oFilaEmisor.Table)
End If
If Not IsNothing(oFilaDatosTransaccion) Then
Datos.clsTransaccion.LiberaTabla(oFilaDatosTransaccion.Table)
End If
If Not IsNothing(oFilaExoneracion) Then
Datos.clsTransaccion.LiberaTabla(oFilaExoneracion.Table)
End If
If lConexionAbierta Then
Call moConexion.Cierra()
End If
moConexion = Nothing
If Me.Trazo Then
Call MyBase.ReportaMensaje("... " & nProcesadas & " transacciones procesadas. " & Me.MemoriaEnUso())
End If
End Sub
#End Region
#Region "Procesos locales"
Private Function MemoriaEnUso() As String
With Process.GetCurrentProcess()
Return "Usando " & Format(.WorkingSet64 / 1024, "#,##0") & "MB."
End With
End Function
Private Function ValorParametro(ByVal acCodigoClase As String _
, ByVal acCodigoParametro As String) As String
With New Datos.clsParametro()
.Conexion = moConexion
Dim oFila As DataRow = .DatosRegistro(acCodigoClase, acCodigoParametro)
If IsNothing(oFila) Then
Return ""
End If
Return oFila(.zAtrValorParametro)
End With
End Function
Private Function TiempoEsperaExoneracion() As Integer
Return Me.ValorParametro(zCodClaseGeneral, zCodParamEsperaExoneracion)
End Function
Private Function DescripcionLineaFacturaSinDetalle() As String
Return Me.ValorParametro(zCodClaseDescLinea, zCodParamSinDetalle)
End Function
Private Function CodigosErrorFacturaProcesada() As String
Return Me.ValorParametro(zCodClaseGeneral, zCodParamCodigoErrProcesada)
End Function
Private Function CreaLineas(ByVal anCodigoCaja As Short _
, ByVal anCodigoTransaccion As Integer _
, ByVal aoFactura As clsFactura _
, ByRef anTotalFacturable As Double _
, ByRef anTotalGravado As Double _
, ByRef alTieneDevoluciones As Boolean) As Boolean
Dim lBien As Boolean = False
Dim oDetalle As New Datos.clsDetalleTransaccion
Dim oTabla As DataTable = Nothing
Try
anTotalGravado = 0
anTotalFacturable = 0
alTieneDevoluciones = False
With New Datos.clsDetalleTransaccion
.Conexion = moConexion
oTabla = .ListaTransaccion(anCodigoCaja, anCodigoTransaccion)
For Each oFila As DataRow In oTabla.Rows
If Not aoFactura.AgregaLinea(oFila(.zAtrNumeroLinea) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCantidad), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrDescUnidad), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrDescLinea), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoProducto), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoDepartamento), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoSeccion), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoTipoTransaccion), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoSubtipoTransaccion), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoEstadoTransaccion), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrMontoTotal), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrTasaImpuesto), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrMontoImpuesto), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrMontoCosto), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoEmpleadoAtoriza), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoTarjetaCliente), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrFacturable), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrGravado), 0) _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoTipoDetalle), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrDescTipoDetalle), "") _
, clsGeneral.ValorSiEsNulo(oFila(.zAtrCodigoOperacion), "")) Then
Return False
End If
' Si la línea es facturable, la suma al total
If clsGeneral.ValorSiEsNulo(oFila(.zAtrFacturable), 0) = 1 Then
anTotalFacturable += clsGeneral.ValorSiEsNulo(oFila(.zAtrMontoTotal), 0)
End If
' Agrega el monto de rubros gravados
anTotalGravado += clsGeneral.ValorSiEsNulo(oFila(.zAtrMontoImpuesto), 0)
' Si no hay encontrado devolociones, verifica la línea para determinar si es una devolución
If Not alTieneDevoluciones Then
alTieneDevoluciones = (clsGeneral.ValorSiEsNulo(oFila(.zAtrDevolucion), 0) = 1)
End If
Next
If aoFactura.TextoLineas <> "" Then
' Si hay al menos una línea facturable en la transacción, no requiere línea única
lBien = True
GoTo EtiSalida
End If
If oTabla.Rows.Count <= 0 Then
GoTo EtiSalida
End If
' Coloca una línea en la factura basada en la primera línea de la transacción
Dim oFilaNueva As DataRow = oTabla.Rows(0)
lBien = aoFactura.AgregaLinea(oFilaNueva(.zAtrNumeroLinea) _
, 0 _
, "" _
, Me.DescripcionLineaFacturaSinDetalle() _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoProducto), "") _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoDepartamento), 0) _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoSeccion), 0) _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoTipoTransaccion), 0) _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoSubtipoTransaccion), 0) _
, clsGeneral.ValorSiEsNulo(oFilaNueva(.zAtrCodigoEstadoTransaccion), 0) _
, 0 _
, 0 _
, 0 _
, 0 _
, 0 _
, "" _
, 1 _
, 0 _
, "" _
, "" _
, "")
oFilaNueva = Nothing
End With
Catch ex As Exception
End Try
EtiSalida:
Call Datos.clsTransaccion.LiberaTabla(oTabla)
oDetalle = Nothing
Return lBien
End Function
Private Function SegundosDesde(ByVal adFecha As Date) As Integer
Dim oDiferencia As TimeSpan = Now.Subtract(adFecha)
Return oDiferencia.Hours * 3600 + oDiferencia.Minutes * 60 + oDiferencia.Seconds
End Function
Private Function FacturaQuedaProcesada(ByVal acTextoSalida As String _
, ByVal acCodigosErrorProcesada As String _
, ByRef acDescripcionError As String) As Boolean
Dim oLector As New IO.StringReader(acTextoSalida)
Dim oDst As New DataSet
Try
' Carga un "dataset" con el contenido del XML de la salida
Call oDst.ReadXml(oLector)
' Obtiene el código de error
Dim cCodigo As String = oDst.Tables("Error").Rows(0)("ErrorCode")
Dim cDescripcion As String = oDst.Tables("Error").Rows(0)("ErrorMessage")
acDescripcionError = cCodigo & ":" & cDescripcion
' Determina si el código está en la lista de códigos que permiten dejar la transacción como procesada
' pendiente de probar
'Return Split(acCodigosErrorProcesada, ",").Contains(cCodigo)
Dim oConsulta = From oCodigo In Split(acCodigosErrorProcesada, ",") _
Where oCodigo.Contains(cCodigo)
Dim lBien As Boolean = (oConsulta.Count > 0)
oConsulta = Nothing
Return lBien
Catch ex As Exception
acDescripcionError = Err.Description
Return False
Finally
Call oLector.Dispose()
oLector = Nothing
Call oDst.Dispose()
oDst = Nothing
End Try
End Function
Private Function ProcesaTransaccion(ByVal aoFilaEmisor As DataRow _
, ByVal aoFilaDatos As DataRow _
, ByVal aoDatosTransaccion As Datos.clsDatosTransaccion _
, ByVal aoFactura As clsFactura _
, ByVal anTotalExonerado As Double _
, ByVal alEstaExonerado As Boolean _
, ByVal aoFilaExoneracion As DataRow) As Boolean
Dim nCodigoSucursal As Short = Val(aoFilaEmisor(clsEmisor.zCodSucursal))
Dim cNombreEmisor As String = aoFilaEmisor(clsEmisor.zCodNombre)
Dim cNombreComercialEmisor As String = aoFilaEmisor(clsEmisor.zCodNombreComercial)
Dim cCedulaEmisor As String = aoFilaEmisor(clsEmisor.zCodCedula)
Dim cTelefonoEmisor As String = aoFilaEmisor(clsEmisor.zCodTelefono)
Dim cFaxEmisor As String = aoFilaEmisor(clsEmisor.zCodFax)
Dim cCorreoEmisor As String = aoFilaEmisor(clsEmisor.zCodCorreo)
Dim cDireccionEmisor As String = aoFilaEmisor(clsEmisor.zCodDireccion)
Dim nTasaImpuesto As Double
With aoDatosTransaccion
Dim dFechaProceso As Date = aoFilaDatos(.zAtrFechaProceso)
Dim dFechaTransaccion As Date = aoFilaDatos(.zAtrFechaTransaccion)
Dim nCodigoCaja As Short = aoFilaDatos(.zAtrCodigoCaja)
Dim nCodigoEmpleado As Short = aoFilaDatos(.zAtrCodigoEmpleado)
Dim nCodigoRegistro As Short = aoFilaDatos(.zAtrCodigoRegistro)
Dim nCodigoTransaccion As Integer = aoFilaDatos(.zAtrCodigoTransaccion)
Dim nTotalFacturable As Double = 0
Dim nTotalGravado As Double = 0
Dim lTieneDevoluciones As Boolean = False
Dim cCodigosError As String = Me.CodigosErrorFacturaProcesada()
Dim lBien As Boolean = False
If Not Me.CreaLineas(nCodigoCaja, nCodigoTransaccion, aoFactura _
, nTotalFacturable, nTotalGravado, lTieneDevoluciones) Then
Call MyBase.ReportaMensaje("No fue posible crear las líneas de la factura para la transacción '" & aoFactura.Codigo & "'.")
GoTo EtiSalida
End If
If Not aoFactura.CreaEncabezado(dFechaProceso, dFechaTransaccion, nCodigoSucursal _
, nCodigoCaja, nCodigoEmpleado, nCodigoRegistro, nCodigoTransaccion _
, cNombreEmisor, cNombreComercialEmisor, cCedulaEmisor _
, cTelefonoEmisor, cFaxEmisor, cCorreoEmisor _
, cDireccionEmisor, aoFilaDatos(.zAtrDescEmpleado) _
, nTasaImpuesto, aoFilaDatos(.zAtrMontoImpuestos) _
, aoFilaDatos(.zAtrMontoTotal), nTotalFacturable, nTotalGravado _
, anTotalExonerado _
, clsGeneral.ValorSiEsNulo(aoFilaDatos(.zAtrCodigoTarjetaCliente), "") _
, clsGeneral.ValorSiEsNulo(aoFilaDatos(.zAtrDescCliente), "") _
, alEstaExonerado, aoFilaExoneracion, lTieneDevoluciones) Then
Call MyBase.ReportaMensaje("No fue posible crear el encabezado de la factura para la transacción '" & aoFactura.Codigo & "'.")
GoTo EtiSalida
End If
Dim lProcesa As Boolean
Dim cSalida As String = ""
RaiseEvent ProcesamientoFacturaSolicitado(aoFactura.Contenido, lProcesa, cSalida)
Dim cDescripcionError As String = ""
If Not lProcesa Then
lProcesa = Me.FacturaQuedaProcesada(cSalida, cCodigosError, cDescripcionError)
End If
If lProcesa Then
If Not Me.CambiaEstadoTransaccion(nCodigoCaja, nCodigoTransaccion) Then
Call MyBase.ReportaMensaje("No fue posible cambiar estado de control para la transacción '" & aoFactura.Codigo & "'.")
GoTo EtiSalida
End If
Else
Call MyBase.ReportaMensaje("No fue posible generar la factura para la transacción '" & aoFactura.Codigo & "'." _
& " " & cDescripcionError)
End If
lBien = True
EtiSalida:
Call aoFactura.Limpia()
Return lBien
End With
End Function
#End Region]

Just a wild guess...
Do you close your database connection at any time?
i see moConexion = New clsConexion(Me.HileraConexion) but nowhere moConexion.Close

Did u also check the current state of the database (like blocking transactions, all transactions and so on)

@GeekByChice: Here are many referenced classes thar are not part of the code shown. If my deduction is right, the clsConexion class is used to manage connections. This class has a Cierra method that 'should' close the connection to the db and is called at the end of Procesa sub.

@Orlando: In the AgregaLinea method of aoFactura, do you use a begin transaction to update the underlying db, then commit the transaction if Ok or else rollback?
I se you use many times the With New sentence. Do you really need a new instance of the object (ie: Datos.clsParametro in the ValorParametro or cambiaEstadoTransaction functions)?

Espero que esto ayude

Thank you GeekByChice. AgregaLinea method does not produce a db transaction, it just creates a string with all data for them to be processed by a third party software. Do you belive that the "with new" sentence produce the growing. We normally avoid using global objets and expect that after disposing or "=nothing" the object the memory would be cleared by the garbage collector procedure. By the way, we even called explicitly the GC. Muchas gracias. Orlando

You are right, The GC 'must' clean the disposed objects using the dispose method or setting it to nothing but read http://msdn.microsoft.com/en-us/library/hks5e2k6(VS.80).aspx for more info on how the GC works.

If it is possible, limit the use of unnecessary 'duplicate' objects in memory.

IE: in the ValorParametro you can pass by reference an existing Datos.clsParametro() avoiding to set the .Conexion at each call.

Just curious, how and when do you release the result of AgregaLinea to the third party software? Do you create a new instance of it for each call to AgregaLinea?

Saludos.
Lola

Thanks very much for the link, it looks quite, I'll read it fully. Isn't it true that using "byVal" in the case of ValorParametro I'm just sending the pointer instead of the whole class?

What the code does is to provide invoice data (header and detail sections) to an electronic invoice software (third party SW). AgregaLinea builds up the detail section on a string. CreaEncabezado builds up the invoice header also on a string. Both strings are concatenated and send to the third party software via an event. The answer: no, we do not create a new instance for each call to AgregaLinea.

This article has been dead for over six months. Start a new discussion instead.