Synergex / HarmonyCore

Harmony Core is a framework that consists of libraries, CodeGen templates, and conventions that enable you to expose Synergy logic and data as a RESTful web service using OData and ASP.NET Core
BSD 2-Clause "Simplified" License
23 stars 14 forks source link

Record locked after patch method #300

Closed sshih-rts closed 1 year ago

sshih-rts commented 1 year ago

Case# 108776

Harmony.Core version [ 3.1.496] Synergex.SynergeDE version [11.1.1070]

We had problem with open channels after the API being called to process multiple orders.

Errors:

at Synergex.SynergyDE.SynChannels.rntchanchecknohook(Int32 chn) at Synergex.SynergyDE.SysRoutines.s_getfa(NumericParam parm1, AlphaDesc parm2, VariantDesc parm3) at Harmony.Core.FileIO.Queryable.QueryBuffer.MakeTypeBuffer(TypeBuffer buf, IDataObjectProvider dataObjectProvider, Dictionary2 bufferMap) at Harmony.Core.FileIO.Queryable.QueryBuffer.$lc1091866946_MakeTypeBuffer.$lm_#1_Func_50(TypeBuffer jBuf) at System.Linq.Enumerable.SelectListIterator2.ToList() at Harmony.Core.FileIO.Queryable.QueryBuffer.MakeTypeBuffer(TypeBuffer buf, IDataObjectProvider dataObjectProvider, Dictionary2 bufferMap) at Harmony.Core.FileIO.Queryable.QueryBuffer..ctor(QueryBuffer bufferProtoType, IDataObjectProvider dataObjectProvider, IReadOnlyDictionary2 params, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.UntypedExecuteJoinCollectionPlan(Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.ExecuteSelectCollectionPlan[T](Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.ExecuteCollectionPlan[T](Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context)


About an year ago, the Synergy consultant helped us to modify the file FileResolverCustom.dbl which extends FileChannelManager by override method ReturnChannel.

    public override method ReturnChannel, void
        required in channelId, int
    proc
        if (Thread.CurrentThread.Name == "GC Finalizer Thread" || Thread.CurrentThread.ManagedThreadId == 2)
        begin
            ;;If we get here then the cnannel is being closed by the garbage collector, 
            ;;suggesting the thread was previously not disposed after use.
            ;;Log that so it can be detected and fixed.
            data fileSpec, string, %isinfoa(channelId,"FILENAME")
            data timestamp, a20, %datetime
            Console.WriteLine("WARNING: At {0} channel {1} file {2} closed by garbage collector suggesting not properly disposed in application.",
            &   %string(^d(timestamp),"XXXX-XX-XX XX:XX:XX"),channelId,fileSpec)

        end
    endmethod

I added a new file to the repository and rebuild the project, and found that record got locked after updating it. If i comment out the overridden method 'ReturnChannel', the record lock problem go resolve. However, the opening channel error appears again.

The patch method is in file SalesorderDetailsController.dbl which is generated automatically after executing regan.bat.

Debug: When I debug the method, after the record got updated, the program goes to the exception section but skipping the 1st line and directly go to mreturn ValidationHelper.ReturnValidationError(ModelState). It does not return any value since ModelState is null. Then it get out of the exception section and go tot he last statement of the method mreturn NoContent(). On Postman, the method patch() finished and status showing 'No Content'. The updated record in VMS is being locked. It can only be unlocked by stopping the HormonyCore API service. No errors from Harmony.Core on the patch method for record lock. The only way we can verify if the record is locked is to inquire it in VMS.

       catch (e, @ValidationException)
        begin
            ModelState.AddModelError("RelationValidation",e.Message)
            **mreturn ValidationHelper.ReturnValidationError(ModelState)**
        end

        endtry

        mreturn NoContent()

Below is the method that updates the record

    public method PatchSalesorderDetail, @IActionResult
        {FromODataUri}
        required in aSalesorderlineid, String
        {FromBody}
        required in aSalesorderDetail, @JsonPatchDocument<SalesorderDetail>
    proc
        ;; Validate inbound data
        if (!ModelState.IsValid)
            mreturn ValidationHelper.ReturnValidationError(ModelState)

        ;;Patch the existing salesorderDetail
        try
        begin
            ;;Get the salesorderDetail to be updated
            data salesorderDetailToUpdate = _DbContext.SalesorderDetails.Find(aSalesorderlineid.PadRight(11))
            data patchError, @JsonPatchError, ^null
            ;;Did we find it?
            if(salesorderDetailToUpdate == ^null)
                mreturn NotFound()

            ;;Apply the changes to the salesorderDetail we read
            aSalesorderDetail.ApplyTo(salesorderDetailToUpdate, lambda(error) { patchError = error })
            ;;if the patchdoc was bad return the error info
            if(patchError != ^null)
                mreturn BadRequest(string.Format("Error applying patch document: error message {0}, caused by {1}", patchError.ErrorMessage, JsonConvert.SerializeObject(patchError.Operation)))

            ;;Update and commit
            _DbContext.SalesorderDetails.Update(salesorderDetailToUpdate)
            _DbContext.SaveChanges()
        end
        catch (e, @InvalidOperationException)
        begin
            mreturn BadRequest(e)
        end
        catch (e, @ValidationException)
        begin
            ModelState.AddModelError("RelationValidation",e.Message)
            mreturn ValidationHelper.ReturnValidationError(ModelState)
        end
        endtry

        mreturn NoContent()

    endmethod
hippiehunter commented 1 year ago

your overriden ReturnChannel method looks like its a logging and diagnostics mechanism, but the call to the base underlying functionality is missing. Please add parent.ReturnChannel(channelId) if you want to make it work. Keep in mind this overriden ReturnChannel is just there to provide logging and verification that the api is being used correctly when its being directly accessed from your C# code. The scenario you have shown in this issue is different and looks like its using the generated API endpoints so I would be surprised if the original locking problem you were looking for a year ago, were to show up here.

sshih-rts commented 1 year ago

Hi Jeff, That fixed the record lock problem. thank you!

However, we still have the open channel issue. The application process several orders, then errors appears. I cannot find the error message from the exception statement we throw 'Channel has not been opened'. Is it from HarmonyCore?

Error message - "Channel has not been opened" error stack trace at Synergex.SynergyDE.SynChannels.rntchanchecknohook(Int32 chn) at Synergex.SynergyDE.SysRoutines.s_getfa(NumericParam parm1, AlphaDesc parm2, VariantDesc parm3) at Harmony.Core.FileIO.Queryable.QueryBuffer.MakeTypeBuffer(TypeBuffer buf, IDataObjectProvider dataObjectProvider, Dictionary2 bufferMap) at Harmony.Core.FileIO.Queryable.QueryBuffer.$lc1091866946_MakeTypeBuffer.$lm_#1_Func_50(TypeBuffer jBuf) at System.Linq.Enumerable.SelectListIterator2.ToList() at Harmony.Core.FileIO.Queryable.QueryBuffer.MakeTypeBuffer(TypeBuffer buf, IDataObjectProvider dataObjectProvider, Dictionary2 bufferMap) at Harmony.Core.FileIO.Queryable.QueryBuffer..ctor(QueryBuffer bufferProtoType, IDataObjectProvider dataObjectProvider, IReadOnlyDictionary2 params, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.UntypedExecuteJoinCollectionPlan(Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.ExecuteSelectCollectionPlan[T](Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context) at Harmony.Core.FileIO.Queryable.PreparedQueryPlan.ExecuteCollectionPlan[T](Func2 tracker, IReadOnlyDictionary2 parameters, IDataObjectProvider dataObjectProvider, Object context)


Below is the method GetChannel from file FileSpecResolverCustom.dbl

public class FileSpecResolverCustom extends FileChannelManager

    public override method GetChannel, int
        required in fileLogical, string
        required in openMode, FileOpenMode
    proc
        ;;In the RSAC repository file names are logical names that must be translated
        ;;into actual file specifications. We will use the Traditional Bridge mechanism
        ;;to call a Traditional Synergy routine (on OpenVMS in this case) to provide the
        ;;actual runtime file specifications that we should use to open the data files.
        Monitor.Enter(mEnvBranchFileSpecs)

        data logicalToTranslate, string, fileLogical

        ;;If the logical name ends with a colon, strip it off before doing the lookup

        if (logicalToTranslate.EndsWith(":"))
            logicalToTranslate = logicalToTranslate.Split(":")[1].ToUpper()

        data FileSpecLookup, @Dictionary<string,string>

        data envBranch, string, Harmony.AspNetCore.MultiTenantProvider.TenantID

        try
        begin
            if (mEnvBranchFileSpecs.ContainsKey(envBranch)) then
            begin
                FileSpecLookup = mEnvBranchFileSpecs[envBranch]
            end
            else
            begin
                FileSpecLookup = new Dictionary<String, String>()

                ;;When the self hosting environment is initialized a collection of all of the
                ;;repository file names of the files exposed by the service is generated, but
                ;;this case those file specs are actually logical names, like "MDS$FILE_08DEV:".
                ;;
                ;;The following code will take that collection of logical names and will pass
                ;;it through to a traditional Synergy routine called GetFileSpecs on OpenVMS.
                ;;That code can be found in the ExposedFunctions folder in the TraditionalBridge
                ;;project. The routine is responsible for translating the logical names into
                ;;the actual file specs to be used.

                ;;Copy the file specs (logical names) from the repository into a new collection
                ;;that we can have VMS code update
                data fileSpec = String.Empty
                data fileSpecs = new List<string>()
                foreach fileSpec in Startup.LogicalNames
                    fileSpecs.Add(fileSpec)

                ;;Call the OpenVMS GetFileSpecs routine to translate them for us.
                data functionInstance = mVmsFunctions.MakeContext(mServices)
                try
                begin
                    data fileSpecTask = functionInstance.GetFilespecs(envBranch,fileSpecs)
                    fileSpecTask.Wait()
                    fileSpecs = fileSpecTask.Result.ToList()

                    ;;Now store the returned file specifications in a string dictionary,
                    ;;indexed by the original logical name (with the : removed) so that
                    ;;we can easily lookup subsequent file specs as they are requested.
                    data ix, int
                    for ix from 0 thru fileSpecs.Count - 1
                    begin
                        FileSpecLookup.Add(Startup.LogicalNames[ix],fileSpecs[ix])
                    end

                    ;;Add the dictionary for this ENV_BRANCH to the master dictionary
                    mEnvBranchFileSpecs.Add(envBranch,FileSpecLookup)

                end
                catch (bex, @BridgeException)
                begin
                    data remoteStackTrace = string.Join(Environment.NewLine, bex.RemoteStackTrace)
                    throw new ApplicationException(String.Format("Failed to call OpenVMS routine %GetFileSpecs. Error was: {0} RemoteStackTrace:{1}",bex.Message, remoteStackTrace))
                end
                catch (ex, @Exception)
                begin
                    throw new ApplicationException(String.Format("Failed to call OpenVMS routine %GetFileSpecs. Error was: {0}",ex.Message))
                end
                finally
                begin
                    mVmsFunctions.ReturnContext(functionInstance)
                end
                endtry
            end

            ;;Lookup the requested filespec and return a channel to the file

            try
            begin
                mreturn parent.GetChannel(FileSpecLookup[logicalToTranslate],openMode)
            end
            catch (e, @Exception)
            begin
                throw new ApplicationException(String.Format("FileSpecResolverCustom has no entry for {0} {1} {2}",envBranch, logicalToTranslate,e.Message))
            end
            endtry
        end
        finally
        begin
            Monitor.Exit(mEnvBranchFileSpecs)
        end
        endtry

    endmethod
hippiehunter commented 1 year ago

If you run your scenario with the debugger attached are there any messages printed in the output window? you can get to the output window from the menu -> View -> Output Window or the default key combo is Ctrl + Alt + O. We may want to consider Upgrading your project to Harmony Core 6, do you have a few hours available sometime next week?

sshih-rts commented 1 year ago

It will be greatly appreciated if we can use some of your time next week. What's the good time for you?

hippiehunter commented 1 year ago

Closing this issue after diagnosing the failure on a call. The issue was caused by custom user code in an async void method throwing an uncaught exception. The logged failure was a byproduct of uncontrolled process termination.