iidec / Integra.Space.Language-upstream

Lenguaje de consulta que permite el acceso a datos en tiempo real
0 stars 0 forks source link

Dispose a los mensajes integra #6

Closed OscarCanek closed 8 years ago

OscarCanek commented 8 years ago

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 tiene group by, solo apply window of, solo from ... select ..., etc.

Pautas a seguir:

Este issue esta relacionado con el issue iidec/Integra.Space.Services#7.

OscarCanek commented 8 years ago

Problema

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.

Objetivo

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.

Contexto

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.

image

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)
        }
    }
}

Conclusión

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.

image

OscarCanek commented 8 years ago

Log

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.

image

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

OscarCanek commented 8 years ago

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.

OscarCanek commented 8 years ago

Árbol de ejecución de la consulta

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

image

Parte repetitiva

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
OscarCanek commented 8 years ago

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.

image

Solución propuesta

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.

OscarCanek commented 8 years ago

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.

OscarCanek commented 8 years ago

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.

OscarCanek commented 8 years ago

Problema con Group by

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:

Ejemplo

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.

Problema

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.

OscarCanek commented 8 years ago

Se ha optimizado la creación de expresiones para las funciones de cadenas de texto:

OscarCanek commented 8 years ago

Opción 1

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
image image

En c# el ToList no es necesario pero con el observable creado con expresiones es una solución al problema de las repeticiones.

Log con 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
OscarCanek commented 8 years ago

Prueba Opción 1

Logs al probar la Opción 1.

Log con ToList dos eventos diferentes

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
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

Log ToList dos eventos iguales

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
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.

marianogenovese commented 8 years ago

Donde se aplico ToList? podrias poner el IL.

OscarCanek commented 8 years ago

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
    }
}
marianogenovese commented 8 years ago

Puedes poner un ejemplo con ToList y sin ToList en c#

OscarCanek commented 8 years ago

Con 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");
                });

Resultado

image

Sin 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");
                });

Resultado

image

OscarCanek commented 8 years ago

Suposición del comportamiento

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).

OscarCanek commented 8 years ago

Opción 2

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.

OscarCanek commented 8 years ago

Pruebas

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.

Llamada a Dispose en el destructor del evento

con llamada a dispose en el destructor del evento y nunca al terminar de procesar el evento

Llamada a Dispose al terminar de procesar el evento en la consulta

con llamada a dispose al terminar de procesar el evento y sin destructor en el evento

Interpretación de resultados (Muy resumido)

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.

Resolución

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.

OscarCanek commented 8 years ago

Problema

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.

OscarCanek commented 8 years ago

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:

OscarCanek commented 8 years ago

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:

  1. FromSelect
  2. FromWhereSelect
  3. FromApplyWindowSelectWithOnlyFunctionsInProjection
  4. FromApplyWindowSelectWithoutOnlyFunctionsInProjection
  5. FromApplyWindowSelectWithoutOnlyFunctionsInProjectionOrderBy
  6. FromWhereApplyWindowGroupBySelect
  7. FromApplyWindowGroupBySelect
  8. FromWhereApplyWindowSelectWithOnlyFunctionsInProjection
  9. FromWhereApplyWindowSelectWithoutOnlyFunctionsInProjection
  10. FromWhereApplyWindowSelectWithoutOnlyFunctionsInProjectionOrderBy
  11. FromApplyWindowGroupBySelectOrderBy
  12. FromWhereApplyWindowGroupBySelectOrderBy

Esto aun no incluye la llamada a Dispose desde el destructor del evento

OscarCanek commented 8 years ago

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.