
JsRT; Betik Sonlandırma
Önceki makalelerimde JsRT (JavaScript RunTime) ile tanışmış ve kendi geliştirdiğimiz bir uygulamada nasıl barındırabileceğimizi görmüştük. Bu makalemde JsRT kullanarak geliştirdiğiniz bir uygulamayı sahaya sunduğunuzda karşınıza gelebilecek önemli bir problemi ve çözümünü paylaşacağım.
Önceki makalemde örneklediğim basit betikte hatırlarsanız, ekrana Merhaba Dünya yazarak uygulamamızı sonlandırmıştık. Sizde kabul edersiniz ki gerçek hayatta bundan çok daha karışık betikler karşımıza çıkacaktır. Hele ki bu betikler bizim tarafımızdan değil de son kullanıcı tarafından yazılıyorsa daha da karışık olabilir. Kimi iş mantıklarında, uygulamamızın sistem kaynaklarını düzgün kullanabilmesi için x dakika sonra ya da kullanıcının bir tuş kombinasyonu ile tetiklemesi ardından betiğimizin sonlanması istenebilir.
Böylesi bir senaryoda, runtime içerisinde çalışmakta olan betiğimizi durdurabilmemizin tek yolu JsRT tarafından bize sunulan ve parametre olarak betiğin çalıştığı runtime’ı kabul eden JsDisableRuntimeExecution fonksiyonudur. Bu fonksiyonun çalışma mantığı aslında olabildiğine basittir; bir durum değişkeninin değerini ayarlar diye düşünebilirsiniz. Asıl iş ise Runtime tarafından çoktan yapılmıştır bile… Runtime, yazdığımız betiği alıp çalıştırabilir kodlara derlerken araya iptal durumunu kontrol eden kodları enjekte eder. Bu sayede betiğimizin normal işleyişi sırasında iptal durumu değiştiğinde bir sonraki kontrol noktasında fark edilecek ve çalışması sonlandırılacaktır. Mantıklı, değil mi!
Tabi ki kaçınılmaz olarak, Runtime tarafından derleme sırasında kodumuza böylesi eklemeler yapılmasının bir performans maliyeti olacaktır. Runtime tasarımında bu durum göz önüne alınarak performans maliyetinin minimuma indirilebilmesi için gerekli önlemler alınmış durumda. Bu önlemlerden en basiti şüphesiz ki her kod satırının ardına böylesi bir kontrol eklememek 😉 Bir başkası ise döngülerde bu kontrolü gerçekleştirmemek. Bu sayede basit döngülerde katlanarak giden bir performans maliyetinin önüne geçilmiş olur. Tabi böyle bir kararın dezavantajıda olmakta; basit döngüler içerisinde betiğimiz sonlandırılamamakta, uygulamamız askıda kalarak hiç bir zaman sona eremeyebilir.
Bir önceki paragrafta bahsettiğim durumun önüne geçebilmekte tabi ki mümkün, bunun için Runtime ilklendirilirken geçeceğimiz JsRuntimeAttributeAllowScriptInterrupt bayrağı ile döngü sonlarında da bu kontrolün yapılmasını sağlayabiliriz. Bu değişikliğin betiğin çalışma performansını az da olsa olumsuz etkileyeceğini söylememe gerek yok sanırım.
Konuyu pekiştirmek adına; bir önceki makalede paylaştığım kodlardan devam ederek, aşağıdaki betiği çalıştırmayı deneyelim;
var i = 0; for (i = 0; i>-1; i++) { alert("Döngü : " + i); }
Sonsuz bir döngü içerisinde ekrana o anki döngü değeri yazılacaktır;

Eğer bir önceki makalemde paylaştığım örnek MSDN kodlarını kullanıyorsanız betiği sonlandırmak için doğrudan JsDisableRuntimeExecution çağrısı yapmanıza gerek olmadığını farketmişsinizdir. Bu iş için JavaScriptRuntime sınıfı içerisinde yer alan Disabled özelliğini kullanabilirsiniz. Bu özellikle kendi içerisinde gerekli fonksiyon çağrılarını yapmakta.
Önceki makalemdeki kodumuzu test amacıyla aşağıdaki şekilde asenkrona dönüştürdüm;
using Enterprisecoding.JSHosting.Hosting; using System; using System.IO; using System.Threading.Tasks; namespace Enterprisecoding.JSHosting { class Program { private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero); private static JavaScriptRuntime calismaZamani; static void Main(string[] args) { if (args == null || args.Length == 0) { Console.Error.WriteLine("Kullanım : JSHosting <betik adı> <parametreler>"); return; } Task<JavaScriptValue?> scriptTask = Task.Factory.StartNew(() => { return BetigiCalistir(args); }); if (scriptTask.IsCompleted) { var sonuc = scriptTask.Result; } else { scriptTask.Wait(300); calismaZamani.Disabled = true; var sonuc = scriptTask.Result; } Console.ReadKey(); } private static JavaScriptValue? BetigiCalistir(string[] args) { using (calismaZamani = JavaScriptRuntime.Create()) { var context = calismaZamani.CreateContext(); using (new JavaScriptContext.Scope(context)) { var globalObject = JavaScriptValue.GlobalObject; var propertyId = JavaScriptPropertyId.FromString("alert"); var function = JavaScriptValue.CreateFunction(Alert); globalObject.SetProperty(propertyId, function, true); var betikAdi = args[0]; var betik = File.ReadAllText(betikAdi); JavaScriptValue? sonuc = null; try { sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi); } catch (JavaScriptScriptException e) { if (e.ErrorCode == JavaScriptErrorCode.ScriptTerminated) { Console.Error.WriteLine("JSHosting: script sonlandırıldı"); } else { var messageName = JavaScriptPropertyId.FromString("message"); var messageValue = e.Error.GetProperty(messageName); var message = messageValue.ToString(); Console.Error.WriteLine("JSHosting: Hata: {0}", message); } } catch (Exception e) { Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message); } return sonuc; } } } private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) { for (uint index = 1; index < argumentCount; index++) { if (index > 1) { Console.Write(" "); } Console.Write(arguments[index].ConvertToString().ToString()); } Console.WriteLine(); return JavaScriptValue.Invalid; } } }
Yukarıdaki kod parçacığında betiğimizi başlattıktan sonra bitip bitmediğini kontrol ederek bitmemesi durumunda tamamlanması için 300 milisaniyelik bir süre veriyorum. Dikkat ederseniz henüz JsRuntimeAttributeAllowScriptInterrupt tanımlamasını yapmadık. Bu kod parçacığını sonsuz döngü ile çalıştırdığımızda betiğin çalışmasının sonlandırılamadığına dair “Cannot disable execution.” hatasını alacağız;

Bu hata mesajını almamak için çalışma zamanını oluşturduğumuz kodu aşağıdaki şekilde güncellemeliyiz;
calismaZamani = JavaScriptRuntime.Create(JavaScriptRuntimeAttributes.AllowScriptInterrupt, JavaScriptRuntimeVersion.Version11)
Dikkat ederseniz Runtime’ı ilklendirirken JsRuntimeAttributeAllowScriptInterrupt belirtmek için JavaScriptRuntimeAttributes.AllowScriptInterrupt kullandık. Ardından kodumuzu çalıştırdığımızda başarılı sonucu görebiliriz;

Koda dikkat ettiyseniz, aslında betik başarıyla sonlandırıldığında da bir hata fırlatılmakta. kodumuz içerisinde bu hata türünü yakalayıp kullanıcıyı uygun şekilde bilgilendiriyoruz.
Toparladığımızda kodumuzun son hali aşağıdaki şekilde olacaktır;
using Enterprisecoding.JSHosting.Hosting; using System; using System.IO; using System.Threading.Tasks; namespace Enterprisecoding.JSHosting { class Program { private static JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero); private static JavaScriptRuntime calismaZamani; static void Main(string[] args) { if (args == null || args.Length == 0) { Console.Error.WriteLine("Kullanım : JSHosting <betik adı> <parametreler>"); return; } Task<JavaScriptValue?> scriptTask = Task.Factory.StartNew(() => { return BetigiCalistir(args); }); if (scriptTask.IsCompleted) { var sonuc = scriptTask.Result; } else { scriptTask.Wait(300); calismaZamani.Disabled = true; var sonuc = scriptTask.Result; } Console.ReadKey(); } private static JavaScriptValue? BetigiCalistir(string[] args) { using (calismaZamani = JavaScriptRuntime.Create(JavaScriptRuntimeAttributes.AllowScriptInterrupt, JavaScriptRuntimeVersion.Version11)) { var context = calismaZamani.CreateContext(); using (new JavaScriptContext.Scope(context)) { var globalObject = JavaScriptValue.GlobalObject; var propertyId = JavaScriptPropertyId.FromString("alert"); var function = JavaScriptValue.CreateFunction(Alert); globalObject.SetProperty(propertyId, function, true); var betikAdi = args[0]; var betik = File.ReadAllText(betikAdi); JavaScriptValue? sonuc = null; try { sonuc = JavaScriptContext.RunScript(betik, currentSourceContext++, betikAdi); } catch (JavaScriptScriptException e) { if (e.ErrorCode == JavaScriptErrorCode.ScriptTerminated) { Console.Error.WriteLine("JSHosting: script sonlandırıldı"); } else { var messageName = JavaScriptPropertyId.FromString("message"); var messageValue = e.Error.GetProperty(messageName); var message = messageValue.ToString(); Console.Error.WriteLine("JSHosting: Hata: {0}", message); } } catch (Exception e) { Console.Error.WriteLine("JSHosting: Betik çalıştırılırken hata oluştu: {0}", e.Message); } return sonuc; } } } private static JavaScriptValue Alert(JavaScriptValue callee, bool isConstructCall, JavaScriptValue[] arguments, ushort argumentCount) { for (uint index = 1; index < argumentCount; index++) { if (index > 1) { Console.Write(" "); } Console.Write(arguments[index].ConvertToString().ToString()); } Console.WriteLine(); return JavaScriptValue.Invalid; } } }