Closed OscarCanek closed 8 years ago
Actualmente existe un problema al llamar al método Dispose
en las consultas con sentencia Group by. Al ejecutar una consulta, procesar eventos y llamar a Dispose
se obtiene el siguiente error:
An exception of type 'System.NullReferenceException' occurred in Integra.Messaging.dll but was not handled in user code
Additional information: Object reference not set to an instance of an object.
Revisando el código generado por la expresión creada durante la compilación se puede observar que el código generado tiene la misma estructura que el observable esperado en c#. El problema parece ser que hace Dispose
sin antes terminar de procesar el conjunto completo de eventos de los grupos, comportamiento que en el observable esperado no ocurre.
Cabe mencionar que la llamada al método
Dispose
en consultas de la forma: from SpaceObservable1 select @event.Message.#1.#18 as MCC; no ocurre ningún problema.
Analizar el origen del error y proponer soluciones para los casos en donde se analizan grupos de eventos, ya que la estructura en los dos códigos sigue el mismo patrón pero el generado a partir de la compilación da error al ejecutarse.
El siguiente EQL es uno de los casos para los que se necesita llamar al método Dispose
de los mensajes en los eventos procesados.
from SpaceObservable1
apply window of '00:00:00:01'
group by @event.Message.#1.CardAcceptorNameLocation as grupo1
select top 1 grupo1 as Llave,
sum((decimal)@event.Message.#1.TransactionAmount) as Sumatoria
order by desc Sumatoria
Nota: es un EQL que contiene Group by
El siguiente código muestra la estructura del observable esperado de la compilación.
SourceTest.source
.Buffer(TimeSpan.FromSeconds(1))
.Select<IList<EventObject>, IEnumerable<Test>>(b =>
{
IEnumerable<Test> resFinal = b
.GroupBy<EventObject, object>(y => new { g1 = y.Message["Body"]["MerchantType"].Value.ToString() }, new GroupByKeyComparer<object>())
.Select<IGrouping<object, EventObject>, Test>(e =>
{
Test t = new Test();
t.A = e.Key;
t.B = e.Sum(e1r => (decimal)e1r.Message[1][4].Value);
e.ToList().ForEach(x => x.Message.Dispose());
return t;
})
.OrderByDescending(x => (decimal)x.B)
.Take(1)
;
return resFinal;
});
El código generado de la expresión creada por el compilador que se necesita analizar se encuentra en el siguiente archivo: Codigo generado de la expresion creada.txt
A continuación se muestran las partes del código de la expresión creada necesarias para entender su estructura:
El siguiente bloque de código representa en rx el select del group by: .Select<IGrouping<object, EventObject>, dynamic>(e =>
$EnumerableGroupBy = .Call System.Linq.Enumerable.Select(
.Block(System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[SpaceDynamicType_36,Integra.Space.Event.EventObject]] $resultGroupByObservable)
{
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable group by")
} .Else {
.Default(System.Void)
};
$resultGroupByObservable = .Call System.Linq.Enumerable.GroupBy(
$var3,
.Lambda #Lambda2<System.Func`2[Integra.Space.Event.EventObject,SpaceDynamicType_36]>,
.New Integra.Space.Language.Runtime.GroupByKeyComparer`1[SpaceDynamicType_36]());
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable group by")
} .Else {
.Default(System.Void)
};
.Default(System.Void)
}
} .Catch (System.Exception $var4) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 52, Instruction: group by @event.Message.#1.CardAcceptorNameLocation as grupo1, Error: RE25: Error with 'group by' function.",
$var4)
}
};
$resultGroupByObservable
},
.Lambda #Lambda3<System.Func`2[System.Linq.IGrouping`2[SpaceDynamicType_36,Integra.Space.Event.EventObject],SpaceDynamicType_642]>)
Lambda3 es el lambda del select
.Lambda #Lambda3<System.Func`2[System.Linq.IGrouping`2[SpaceDynamicType_36,Integra.Space.Event.EventObject],SpaceDynamicType_642]>(System.Linq.IGrouping`2[SpaceDynamicType_36,Integra.Space.Event.EventObject] $var14)
$var14 en el bloque anterior es la variable e
en el observable.
La parte del observable e.ToList().ForEach(x => x.Message.Dispose());
se encuentra en el siguiente bloque:
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable to list")
} .Else {
.Default(System.Void)
};
.Call (.Block(System.Collections.Generic.List`1[Integra.Space.Event.EventObject] $ToListParam) {
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable to list")
} .Else {
.Default(System.Void)
};
.If ($var14 != null) {
$ToListParam = .Call System.Linq.Enumerable.ToList($var14)
} .Else {
.Default(System.Void)
};
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable to list")
} .Else {
.Default(System.Void)
};
.Default(System.Void)
}
} .Catch (System.Exception $var19) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 114, Instruction: select top listOfValues: numero grupo1 as Llave, sum((decimal)@event.Message.#1.TransactionAmount) as Sumatoria, Error: RE54: Error with ToList method.",
$var19)
}
};
$ToListParam
}).ForEach(.Lambda #Lambda6<System.Action`1[Integra.Space.Event.EventObject]>);
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable to list")
} .Else {
.Default(System.Void)
}
}
}
El código que precede al código del bloque anterior es el setteo de las propiedades del objeto del tipo creado para la proyección con los valores resultantes, como se muestra en la siguiente imagen.
Lambda6 es la llamada al método Dispose
de cada mensaje de grupo, representa x => x.Message.Dispose()
donde $ForEachParam
es igual a x
.Lambda #Lambda6<System.Action`1[Integra.Space.Event.EventObject]>(Integra.Space.Event.EventObject $ForEachParam) {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("++++++++++++++++++++")
} .Else {
.Default(System.Void)
};
.If ($ForEachParam != null) {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("**************")
} .Else {
.Default(System.Void)
};
.If (.Call $ForEachParam.get_Message() != null) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Hará dispose del mensaje.");
.Call ($ForEachParam.Message).Dispose();
.Call System.Diagnostics.Debug.WriteLine("Hizo dispose del mensaje.")
}
} .Else {
.Default(System.Void)
}
}
} .Else {
.Default(System.Void)
}
}
}
La estructura del código generado sigue el mismo patrón que la estructura del código del observable esperado, sin embargo el código generado por la compilación da error al ejecutarse.
Luego de analizar los logs generados sin llamar a Dispose (izquierda)
y con la llamada a Dispose (derecha)
, el comportamiento es el siguiente: sigue accediendo a los mensajes de los eventos luego de hacer la proyección y el order by
, exactamente para este caso accede tres veces mas a los mensajes.
Linea azul: acceso a la propiedad Sumatoria de la proyección. Amarillo: llamada al método Dispose. Rojo: error
La linea 108
muestra el final de la llamada a la función sum
de la proyección. Luego en el lado izquierdo accede a la propiedad sumatoria, en el lado derecho hace la llamada a Dispose
y después accede a la propiedad sumatoria. Por último en ambos casos sigue accediendo a las propiedades del mensaje de los eventos y es allí donde lanza el error debido a que ya se ha llamado a Dispose
.
El archivo de log completo es el siguiente: LogDeEjecucion.txt
Lo anterior descrito descarta la posibilidad de que se esté llamando al método Dispose
en el lugar incorrecto. Ahora se debe encontrar la causa del comportamiento que se describe anteriormente, el por qué accede cuatro veces al mensaje de los eventos. Esa es la razón por la que la estructura del código generado por el compilador es igual a la estructura del observable esperado y aun así no esté funcionando.
from SpaceObservable1
apply window of '00:00:00:01'
group by @event.Message.#1.CardAcceptorNameLocation as grupo1
select top 1 grupo1 as Llave,
sum((decimal)@event.Message.#1.TransactionAmount) as Sumatoria
order by desc Sumatoria
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
Start of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
End of the get group key property operation: grupo1
Start of the 'sum' function
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: TransactionAmount
You will get the value of @event.Message.#1.TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: TransactionAmount
The value of @event.Message.#1.TransactionAmount is: 1
Unbox a System.Decimal of the following value:
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: TransactionAmount
You will get the value of @event.Message.#1.TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
You will get the field: TransactionAmount
You will get the message
The message was obtained
You will get the part: 1
You will get the message
The message was obtained
The part was obtained: 1
The field was obtained: TransactionAmount
The value of @event.Message.#1.TransactionAmount is: 1
End of the 'Unbox' operation to type System.Decimal
End of the 'sum' function
Start of the get property operation: Sumatoria
End of the get property operation: Sumatoria
Analizando a mas detalle el código generado se detectó que el problema se encuentra en la forma en que se utilizan los nodos izquierdo y derecho en los nodos padre, por ejemplo:
El siguiente código obtiene el MessagePart de un Message
private Expression GenerateObjectPart(PlanNode actualNode, Expression mensaje, Expression partId)
{
ParameterExpression v = Expression.Parameter(typeof(MessagePart), "bloque");
ParameterExpression paramException = Expression.Variable(typeof(Exception));
ConstantExpression auxPart = (ConstantExpression)partId;
Type tipo = auxPart.Type;
try
{
Expression parte = Expression.Block(
new ParameterExpression[] { v },
Expression.IfThen(
Expression.Call(
mensaje,
typeof(Message).GetMethod("Contains", new Type[] { tipo }),
auxPart
),
Expression.TryCatch(
Expression.Block(
Expression.IfThen(Expression.Constant(this.printLog), Expression.Call(typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(object) }), Expression.Constant("You will get the part: " + auxPart.Value))),
Expression.Assign(v, Expression.Call(mensaje, typeof(Message).GetMethod("get_Item", new Type[] { tipo }), auxPart)),
Expression.IfThen(Expression.Constant(this.printLog), Expression.Call(typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(object) }), Expression.Constant("The part was obtained: " + auxPart.Value)))
),
Expression.Catch(
paramException,
Expression.Block(
Expression.Call(typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(object) }), Expression.Constant("No fue posible obtener la sección del objeto, error en la linea: " + actualNode.Line + " columna: " + actualNode.Column + " con " + actualNode.NodeText)),
Expression.Throw(Expression.New(typeof(RuntimeException).GetConstructor(new Type[] { typeof(string), typeof(Exception) }), Expression.Constant(Resources.SR.RuntimeError(actualNode.Line, actualNode.Column, Resources.RUNTIME_ERRORS.RE31(actualNode.NodeText), actualNode.NodeText), typeof(string)), paramException))
)
)
)
),
v);
return parte;
}
catch (Exception e)
{
throw new CompilationException(Resources.SR.CompilationError(actualNode.Line, actualNode.Column, Resources.COMPILATION_ERRORS.CE33(actualNode.NodeText), actualNode.NodeText), e);
}
}
El problema se detectó en la forma en que se utiliza el parámetro mensaje
. En código de alto nivel C#
es una variable común pero internamente contiene todo el código de acceso al mensaje y en un nivel mas bajo el código de acceso al evento, y así sucesivamente. Por lo tanto hace todos esos procesos las veces que es llamado y de allí es el origen del comportamiento repetitivo del mensaje de log
: You will get the message
.
Actualizar la creación de las expresiones para que realice una sola vez cada procedimiento. Se empezará con el acceso a las propiedades de los eventos y mensajes.
Se ha actualizado la forma en que se crean las expresiones para acceder a las propiedades de los mensajes de los eventos, esto mejorará el rendimiento de la ejecución de expresiones significativamente. Ahora la parte repetitiva resultante es la siguiente:
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the 'sum' function
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: TransactionAmount
The field was obtained: TransactionAmount
You will get the value of @event.Message.#1.TransactionAmount
The value of @event.Message.#1.TransactionAmount is: 1
Unbox a System.Decimal of the following value:
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: TransactionAmount
The field was obtained: TransactionAmount
You will get the value of @event.Message.#1.TransactionAmount
The value of @event.Message.#1.TransactionAmount is: 1
End of the 'Unbox' operation to type System.Decimal
End of the 'sum' function
Start of the get property operation: Sumatoria
End of the get property operation: Sumatoria
Se mejoró de tener 100 lineas de mensajes de log a 34 lineas de mensajes de log, es decir, se mejoró el acceso a las propiedades de los mensajes de los eventos entrantes
Cabe mencionar que aun se repite el bloque anterior N veces.
También se ha optimizado la creación de la expresión para conversión de tipos, específicamente el la expresión resultante del método GenerateCast
de la clase ObservableConstructor
.
Al colocarte a la consulta la sentencia Group by
repite dos veces el obtener los grupos y los valores a proyectar de la siguiente forma:
Consulta a la que se le envían dos eventos (con un solo evento pasa lo mismo):
"from SpaceObservable1
apply window of '00:00:00:01'
group by @event.Message.#1.CardAcceptorNameLocation as grupo1, @event.Message.#1.#18 as MCC
select grupo1 as tipoMensaje, MCC as MerchantCategoryCode"
El log de la consulta anterior se muestra a continuación:
Start of the select for observable buffer sin lambda
Start of the 'apply window of' function.
End of the 'apply window of' function.
End of the select for observable buffer sin lambda
Start of the select for enumerable group by
Start of the enumerable group by
End of the enumerable group by
End of the select for enumerable group by
------ INICIA SECCIÓN DE INTERÉS ------
####### INICIA: BLOQUE QUE SE DUPLICA #######
*** INICIA: obtiene valores del grupo ***
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo2HONDURAS HN
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
*** TERMINA: obtiene valores del grupo ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
####### TERMINA: BLOQUE QUE SE DUPLICA #######
####### INICIA: BLOQUE DUPLICADO #######
*** INICIA: obtiene valores del grupo ***
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo2HONDURAS HN
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
*** TERMINA: obtiene valores del grupo ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
####### TERMINA: BLOQUE DUPLICADO #######
------ TERMINA SECCIÓN DE INTERÉS ------
El bloque de código que generar los prints de cada sección de la parte repetida es el siguiente:
$SelectForEnumerableGroupBy = .Call System.Linq.Enumerable.Select(
.Block(System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[SpaceDynamicType_460,Integra.Space.Event.EventObject]] $ResultEnumerableGroupBy)
{
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable group by")
} .Else {
.Default(System.Void)
};
$ResultEnumerableGroupBy = .Call System.Linq.Enumerable.GroupBy(
$var3,
.Lambda #Lambda2<System.Func`2[Integra.Space.Event.EventObject,SpaceDynamicType_460]>,
.New Integra.Space.Language.Runtime.GroupByKeyComparer`1[SpaceDynamicType_460]());
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable group by")
} .Else {
.Default(System.Void)
}
}
} .Catch (System.Exception $var4) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 52, Instruction: group by @event.Message.#1.CardAcceptorNameLocation as grupo1, @event.Message.#1.#18 as MCC, Error: RE25: Error with 'group by' function.",
$var4)
}
};
$ResultEnumerableGroupBy
},
.Lambda #Lambda3<System.Func`2[System.Linq.IGrouping`2[SpaceDynamicType_460,Integra.Space.Event.EventObject],SpaceDynamicType_896]>)
;
Donde Lambda2
es donde crea los grupos accediendo a los valores de las propiedades de los mensajes y Lambda3
es donde se obtienen los valores a proyectar.
Este comportamiento se ha detectado únicamente al colocar la sentencia Group by
en la consulta.
Cabe destacar que $SelectForEnumerableGroupBy
únicamente se utiliza tres veces: cuando se define, cuando se le asigna el valor y cuando se retorna; es decir, se utiliza solo una vez pero por lo impreso en el log se esta ejecutando dos veces.
Se ha optimizado la creación de expresiones para las funciones de cadenas de texto:
Las repeticiones se evitan agregando una expresión ToList
después del Select
del Group by
en el ObservableConstructor
. El código c# homólogo sería el siguiente:
Sin ToList |
Con ToList |
---|---|
En c# el
ToList
no es necesario pero con el observable creado con expresiones es una solución al problema de las repeticiones.
ToList
Start of the select for observable buffer sin lambda
Start of the 'apply window of' function.
End of the 'apply window of' function.
End of the select for observable buffer sin lambda
Start of the select for enumerable group by
Start of the enumerable group by
End of the enumerable group by
End of the select for enumerable group by
Start of the enumerable to list
####### INICIA: BLOQUE QUE SE DUPLICABA #######
*** INICIA: obtiene valores del grupo ***
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: AcquiringInstitutionCountryCode
The field was obtained: AcquiringInstitutionCountryCode
You will get the value of @event.Message.#1.AcquiringInstitutionCountryCode
The value of @event.Message.#1.AcquiringInstitutionCountryCode is: 320
End of the projection
*** TERMINA: obtiene valores del grupo ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
####### INICIA: BLOQUE QUE SE DUPLICABA #######
End of the enumerable to list
Logs al probar la Opción 1
.
ToList
dos eventos diferentesStart of the select for observable buffer sin lambda
Start of the 'apply window of' function.
End of the 'apply window of' function.
End of the select for observable buffer sin lambda
Start of the select for enumerable group by
Start of the enumerable group by
End of the enumerable group by
End of the select for enumerable group by
Start of the enumerable to list
####### INICIA: BLOQUE QUE SE DUPLICABA #######
*** INICIA: obtiene valores del grupo ***
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo2HONDURAS HN
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
*** TERMINA: obtiene valores del grupo ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
####### INICIA: BLOQUE QUE SE DUPLICABA #######
End of the enumerable to list
ToList
dos eventos igualesStart of the select for observable buffer sin lambda
Start of the 'apply window of' function.
End of the 'apply window of' function.
End of the select for observable buffer sin lambda
Start of the select for enumerable group by
Start of the enumerable group by
End of the enumerable group by
End of the select for enumerable group by
Start of the enumerable to list
####### INICIA: BLOQUE QUE SE DUPLICABA #######
*** INICIA: obtiene valores del grupo ***
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
Start of the projection
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: CardAcceptorNameLocation
The field was obtained: CardAcceptorNameLocation
You will get the value of @event.Message.#1.CardAcceptorNameLocation
The value of @event.Message.#1.CardAcceptorNameLocation is: Shell El Rodeo1GUATEMALA GT
You will get the message
The message was obtained
You will get the part: 1
The part was obtained: 1
You will get the field: 18
The field was obtained: 18
You will get the value of @event.Message.#1.#18
The value of @event.Message.#1.#18 is: 6011
End of the projection
*** TERMINA: obtiene valores del grupo ***
*** INICIA: obtiene valores de la proyección ***
Start of the projection
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: grupo1
End of the get group key property operation: grupo1
Start of the get group key operation: Key
End of the get group key operation: Key
Start of the get group key property operation: MCC
End of the get group key property operation: MCC
AQUI LLAMARÍA a DIPOSE
End of the projection
*** TERMINA: obtiene valores de la proyección ***
####### INICIA: BLOQUE QUE SE DUPLICABA #######
End of the enumerable to list
También se probó agregando la llamada al método Dispose
y no ocurrió ningún error.
Donde se aplico ToList? podrias poner el IL.
Lambda1 es el lambda del Select
del buffer. El código completo se encuentra en el siguiente archivo:
ToListTest.txt
La variable $ToListParam
se le asigna el resultado de la llamada al método ToList
Ignorar los prints con símbolos, servían para referencia y saber por donde estaba pasando, estos se eliminarán al resolver el problema.
.Lambda #Lambda1<System.Func`2[System.Collections.Generic.IList`1[Integra.Space.Event.EventObject],System.Collections.Generic.List`1[SpaceDynamicType_409]]>(System.Collections.Generic.IList`1[Integra.Space.Event.EventObject] $var3)
{
.Block(System.Collections.Generic.List`1[SpaceDynamicType_409] $LambdaProjectionSelectForObservableBufferOrSource) {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::")
} .Else {
.Default(System.Void)
};
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
} .Else {
.Default(System.Void)
};
$LambdaProjectionSelectForObservableBufferOrSource = .Block(
System.Collections.Generic.IEnumerable`1[SpaceDynamicType_409] $ObjetoAConvertirALista,
System.Collections.Generic.List`1[SpaceDynamicType_409] $ToListParam) {
$ObjetoAConvertirALista = .Block(System.Collections.Generic.IEnumerable`1[SpaceDynamicType_409] $SelectForEnumerableGroupBy)
{
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
} .Else {
.Default(System.Void)
};
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the select for enumerable group by")
} .Else {
.Default(System.Void)
};
$SelectForEnumerableGroupBy = .Call System.Linq.Enumerable.Select(
.Block(System.Collections.Generic.IEnumerable`1[System.Linq.IGrouping`2[SpaceDynamicType_425,Integra.Space.Event.EventObject]] $ResultEnumerableGroupBy)
{
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable group by")
} .Else {
.Default(System.Void)
};
$ResultEnumerableGroupBy = .Call System.Linq.Enumerable.GroupBy(
$var3,
.Lambda #Lambda2<System.Func`2[Integra.Space.Event.EventObject,SpaceDynamicType_425]>,
.New Integra.Space.Language.Runtime.GroupByKeyComparer`1[SpaceDynamicType_425]());
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable group by")
} .Else {
.Default(System.Void)
}
}
} .Catch (System.Exception $var4) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 52, Instruction: group by @event.Message.#1.CardAcceptorNameLocation as grupo1, @event.Message.#1.#18 as MCC, Error: RE25: Error with 'group by' function.",
$var4)
}
};
$ResultEnumerableGroupBy
},
.Lambda #Lambda3<System.Func`2[System.Linq.IGrouping`2[SpaceDynamicType_425,Integra.Space.Event.EventObject],SpaceDynamicType_409]>)
;
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the select for enumerable group by")
} .Else {
.Default(System.Void)
}
}
} .Catch (System.Exception $var5) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 0, Instruction: , Error: "Error with 'select' function."",
$var5)
}
};
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
} .Else {
.Default(System.Void)
};
$SelectForEnumerableGroupBy
};
.Try {
.Block() {
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("Start of the enumerable to list")
} .Else {
.Default(System.Void)
};
.If ($ObjetoAConvertirALista != null) {
$ToListParam = .Call System.Linq.Enumerable.ToList($ObjetoAConvertirALista)
} .Else {
.Default(System.Void)
};
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("End of the enumerable to list")
} .Else {
.Default(System.Void)
}
}
} .Catch (System.Exception $var6) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 0, Instruction: , Error: RE54: Error with ToList method.",
$var6)
}
};
$ToListParam
};
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")
} .Else {
.Default(System.Void)
}
}
} .Catch (System.Exception $var7) {
.Block() {
.Call System.Diagnostics.Debug.WriteLine("Error");
.Throw .New Integra.Space.Language.Exceptions.RuntimeException(
"RuntimeException: Line: 0, Column: 0, Instruction: from SpaceObservable1 apply window of '00:00:00:01' group by @event.Message.#1.CardAcceptorNameLocation as grupo1, @event.Message.#1.#18 as MCC select grupo1 as tipoMensaje, MCC as MerchantCategoryCode, Error: "Error with 'select' function."",
$var7)
}
};
.If (
True
) {
.Call System.Diagnostics.Debug.WriteLine("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
} .Else {
.Default(System.Void)
};
$LambdaProjectionSelectForObservableBufferOrSource
}
}
Puedes poner un ejemplo con ToList y sin ToList en c#
ToList
SourceTest.source
.Buffer(TimeSpan.FromSeconds(1))
.Select<IList<EventObject>, IEnumerable<Test>>(b =>
{
return b
//.GroupBy<EventObject, object>(y => new { g1 = y.Message["Body"]["MerchantType"].Value.ToString() }, new GroupByKeyComparer<object>())
.GroupBy<EventObject, object>(y => y.Message["Body"]["MerchantType"].Value.ToString())
.Select<IGrouping<object, EventObject>, Test>(e =>
{
Test t = new Test();
t.A = e.Key;
t.B = e.Sum(e1r => { return (decimal)e1r.Message[1][4].Value; });
e.ToList().ForEach(x =>
{
Console.WriteLine("Start Dispose");
x.Message.Dispose();
Console.WriteLine("End Dispose");
});
return t;
})
.ToList()
//.OrderByDescending(x => (decimal)x.B)
//.Take(1)
;
})
.Subscribe(b =>
{
Console.WriteLine("I");
foreach (Test o in b)
{
Console.WriteLine("***********************");
Console.WriteLine("Key: {0}, Suma: {1}", o.A, o.B);
Console.WriteLine("+++++++++++++++++++++++");
}
Console.WriteLine("E");
});
ToList
SourceTest.source
.Buffer(TimeSpan.FromSeconds(1))
.Select<IList<EventObject>, IEnumerable<Test>>(b =>
{
return b
//.GroupBy<EventObject, object>(y => new { g1 = y.Message["Body"]["MerchantType"].Value.ToString() }, new GroupByKeyComparer<object>())
.GroupBy<EventObject, object>(y => y.Message["Body"]["MerchantType"].Value.ToString())
.Select<IGrouping<object, EventObject>, Test>(e =>
{
Test t = new Test();
t.A = e.Key;
t.B = e.Sum(e1r => { return (decimal)e1r.Message[1][4].Value; });
e.ToList().ForEach(x =>
{
Console.WriteLine("Start Dispose");
x.Message.Dispose();
Console.WriteLine("End Dispose");
});
return t;
})
//.OrderByDescending(x => (decimal)x.B)
//.Take(1)
;
})
.Subscribe(b =>
{
Console.WriteLine("I");
foreach (Test o in b)
{
Console.WriteLine("***********************");
Console.WriteLine("Key: {0}, Suma: {1}", o.A, o.B);
Console.WriteLine("+++++++++++++++++++++++");
}
Console.WriteLine("E");
});
El observable sin el ToList da la impresión de que regresa a ejecutar el grupo del observable por cada print en la suscripción. En cambio, con el ToList primero procesa todos los eventos entrantes para luego imprimir únicamente de los resultados obtenidos.
El comportamiento del observable sin el ToList no lanza ningún error al llamar a Dispose debido a que hace Dispose a un grupo de eventos específico y no del conjunto global de eventos entrante. Lo extraño esta en que en el observable construido en la compilación también debe comportarse de la misma forma, aunque en la pruebas realizadas con un solo grupo de eventos este seguía repitiendo el bloque del grupo y la proyección tal como se demostró en los posts anteriores. Sin embargo, al hacer las pruebas con mas de un grupo con el observable construido en compilación este parece comportarse de la forma en que se comporta el observable en c# sin el ToList; dicha prueba puede verse en el post Prueba Opción 1
-> Log con ToList dos eventos diferentes
, y tiene dos bloques de proyección (uno por cada grupo).
Llamar a Dispose
desde el destructor del EventObject
.
/// <summary>
/// Finalizes an instance of the <see cref="EventObject"/> class
/// </summary>
~EventObject()
{
if (this.message != null)
{
this.message.Dispose();
Console.WriteLine("Se hizo Dipose del mensaje desde el destructor del evento.");
System.Diagnostics.Debug.WriteLine("Se hizo Dipose del mensaje desde el destructor del evento.");
System.Diagnostics.Trace.WriteLine("Se hizo Dipose del mensaje desde el destructor del evento.");
}
else
{
Console.WriteLine("El mensaje es nulo.");
System.Diagnostics.Debug.WriteLine("El mensaje es nulo.");
System.Diagnostics.Trace.WriteLine("El mensaje es nulo.");
}
}
Esta es otra opción, no descarta el llamar a
Dispose
al final de procesar el evento en la consulta.
Llamar al método Dispose del mensaje al momento de terminar de procesar el evento es mas eficiente que únicamente hacerlo en el destructor dejar que se ejecute por el GC. En las siguientes imagenes se muestra la cantidad de memoria utilizada por cada uno de los métodos.
Como se muestra en las imágenes, al llamar a Dispose al terminar de procesar el evento reduce a la mitad el consumo de memoria, ademas de que la gráfica de la segunda imagen muestra mayor estabilidad en el uso de la memoria.
En base a las pruebas realizadas se hará lo siguiente: se dejará la llamada a Dispose al terminar de procesar el evento, ademas de la llamada a Dispose en el destructor como método de seguridad para consolidar que se haga Dispose a los mensajes si por algún motivo no es llamado al terminar de procesar el evento.
Al dejar los dos métodos para llamar a Dispose ocurre el siguiente error:
System.NullReferenceException: Object reference not set to an instance of an object.
at Integra.Messaging.Message.GetEnumerator()
at Integra.Messaging.Message.Dispose(Boolean disposing)
at Integra.Messaging.Message.Dispose()
at Integra.Space.Event.EventObject.Finalize()
Parece que el error sucede al momento de llamar a Dispose desde el destructor, por lo tanto hay que cambiar la forma en que se hace dispose del mensaje a un patrón mas seguro, es decir, solo hacer dispose si no ha sido llamado previamente.
Ademas de otros se propone utilizar el método GC.SuppressFinalize. Algunos enlaces con teoria respecto de como mejorar la implementación de IDisposable
se listan a continuación:
Ya fue agregado correctamente en cada tipo de consulta la llamada al método Dispose del mensaje en el observable construido en la compilación. Todas las pruebas unitarias están en verde y el rastreo con el log de cada tipo de consulta imprimió el mensaje indicando que se llamó al método Dispose. Tipo de consulta se refiere a cada estructura de árbol de ejecución que sigue algún patrón, a continuación se lista cada uno:
Esto aun no incluye la llamada a Dispose desde el destructor del evento
Se han realizado pruebas de rendimiento luego de haber implementado la llamada al método Dispose
de los mensajes dentro del método Unlock
de los eventos. Este issue esta relacionado con el issue #8.
A la espera de la confirmación de @marianogenovese para cerrar el issue.
Como parte de la optimización en el procesamiento de eventos, se debe llamar al método
Dispose
de los mensajes Integra que ya hayan sido procesados por el observable. Para realizar esta tarea se debe agregar al árbol de ejecución de cada sentencia los nodos necesarios para hacer la llamada al método previamente mencionado, dependiendo si la sentencia tienegroup by
, soloapply window of
, solofrom ... select ...
, etc.Pautas a seguir:
Dispose
del mensaje de cada evento procesado.Dispose
con Expression trees.Este issue esta relacionado con el issue iidec/Integra.Space.Services#7.