zzzprojects / Eval-Expression.NET

C# Eval Expression | Evaluate, Compile, and Execute C# code and expression at runtime.
https://eval-expression.net/
Other
449 stars 86 forks source link

MemoryLeak: if add EvalContext as GlobalVariable #94

Closed cuiliang closed 3 years ago

cuiliang commented 3 years ago

Code:

using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Z.Expressions;

namespace MemoryTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < 1000; i++)
            {
                var _evalContext = new EvalContext();

                _evalContext.RegisterType(
                    typeof(Regex),
                    typeof(Path),
                    typeof(System.Linq.Enumerable),
                    typeof(DateTime));

                //
                // test 1
                //

                // register evalcontext as variable
                _evalContext.RegisterGlobalVariable("_eval", _evalContext);
                var result = _evalContext.Execute("1+1");
                _evalContext.UnregisterGlobalVariable("_eval");
                _evalContext.UnregisterAll();
                _evalContext.Dispose();

                ////
                //// test 2
                ////
                //var context = new Context() {EvalContext = _evalContext};
                //_evalContext.RegisterGlobalVariable("_context", context);

                //var result = _evalContext.Execute("1+1");
                //_evalContext.UnregisterGlobalVariable("_context");
                //_evalContext.UnregisterAll();
                //_evalContext.Dispose();
            }

            MessageBox.Show("Ok!");
        }
    }

    public class Context
    {
        public EvalContext EvalContext { get; set; }
    }
}

Compare between after first and thrid button click. image

cuiliang commented 3 years ago

Sample project MemoryTest.zip

JonathanMagnan commented 3 years ago

Hello @cuiliang ,

Thank for reporting.

We will look at it to see if we missed something in the dispose part.

If you don't want the current behavior, you can disable the cache:

_evalContext.UseCache = false;

Best Regards,

Jon

cuiliang commented 3 years ago

@JonathanMagnan Thank you! UseCache = false; works.

JonathanMagnan commented 3 years ago

Hello @cuiliang ,

After review, we find out that the cache is global by default. Which explain why object are not disposed.

However, you can use a local cache with the UseLocalCache option:

var _evalContext = new EvalContext();
_evalContext.UseLocalCache = true;

When disposing, everything will be correctly disposed since the cache is local and not global.

It has been a long time, so I'm not exactly sure why we made the cache global by default, it might just because of backward compatibility reason.

Let me know if that answer correctly to the current issue.

Best Regards,

Jon

cuiliang commented 3 years ago

Thank you~

cuiliang commented 3 years ago

What's the cache used for? There seems no document about it.

JonathanMagnan commented 3 years ago

The cache is used to faster execute a compiled expression.

So for example, if you can from time to time a method that execute always the same expression but with different parameter, this expression will be compiled only once. All subsequent execution will take the compiled code from the cache.

So for performance reason.