Friday, 2 October 2015

Unable to move or delete files after reading it

Applies to: AX2012

Scenario
- Open file and read
- Move the file to another folder after reading is completed

Issue
While not running it in CIL, the code works, but once run it in CIL, it give the error "System.IO.IOException: The process cannot access the file because it is being used by another process".

Those who came from AX 2009 used to depends on AX to finalize an object by just leaving the scope. Ref: https://msdn.microsoft.com/en-us/library/io.finalize.aspx

But when running in CIL, the finalize needs to be called manually.

Below is a sample service class called by a controller, when running the code in CIL, if the 'finalize()' is called, no error occur. But if the 'finalize()' is not called, it give the error as shown above.

=================================================
class DemoService extends SysOperationServiceBase
{
}

=================================================
public void processDemo(DemoDataContract _contract)
{
    ;

    this.processFile(_contract.parmFileNameFrom());
    
    this.moveFile(_contract.parmFileNameFrom(), _contract.parmFileNameTo());
}

=================================================
void processFile(Filename _fileNameFrom)
{
    #File
    FileIoPermission                perm;
    TextIo                          importFile;
    container                       record;
    int                             totalRecords;
    ;

    perm = new FileIoPermission(_fileNameFrom, #io_read);
    perm.assert();

    importFile = new TextIo(_fileNameFrom, #io_read);

    while(importFile.status() ==  IO_Status::Ok)
    {
        record = importFile.read();

        totalRecords++;
    }
        
    if(totalRecords)
        info(strFmt("Row count: %1", totalRecords));

    //importFile.finalize(); //Enable and disable this comment to test the effect
}

=================================================
void moveFile(Filename _sourceFile, Filename _destinationFile)
{
    #File
    FileIoPermission            perm;
    ;

    perm = new FileIoPermission(_destinationFile, #io_write);
    perm.assert();

    System.IO.File::Move(_sourceFile, _destinationFile);
}


Friday, 8 May 2015

AIF process stuck / never ending (due to AIFResponse deletion)

Scenario of the issue
  • Created a document service (Eg. TableA) and Inbound port to test it
  • While running the test, the receive (AifGateWayReceiveService) is OK, but the processing (AifInboundProcessingService) seems to stuck there / never ending

In details
  • Start up 2 AX client, Eg ClientA and ClientB.
  • ClientA: The testing starts with getting the Inbound port (file adaptor) and XML file ready
  • Dump the XML into the inbound folder
  • ClientA: Run a simple job which contain this line new AifGateWayReceiveService().run();
  • ClientA: Check "Queue manager" (Path: System administration > Periodic > Service and AIF) and confirmed the message has gone in
  • ClientB: Run another simple job which contain this line new AifInboundProcessingService().run();
  • ClientA: Check the "Queue manager" and confirmed the line is gone and a new record has been inserted into the table used by the document service (Eg. TableA)
  • ClientB: The job seems not finishing, AX is frozen and not responding

Below is a sample of the simple job used in above steps (just toggle around the options).

boolean runReceive = true,
        runProcess = false;
    
if(runReceive)
{
    new AifGateWayReceiveService().run();
}
    
if(runProcess)
{
    new AifInboundProcessingService().run();
}


Investigation result
The frozen isn't actually hanging, instead, it is running some record deletion, which took AGES, hence, not responding.

Below is the Trace Parser screenshot showing the trace file captured for around 30 seconds.

Within this short period, there're thousands of calls to database server for a DELETE statement.

This DELETE statement came from the AIF which tries to delete the expired AIFResponse records.



This DELETE statement supposed to delete any records that's older than the expired datetime (used today's datetime minus the AIFGlobalSettings.ResponseCacheLifetime value). Typically a 24 hours timeframe is a common value. But this table contains millions of records (up to 2 years old of data is in this table).

The solution is to get rid to this data from SQL. Since this is development environment, truncate table would do. But if this production environment, it would need more care on how the data is deleted (performance, time of performing the deletion, recover mode & backup chain, etc).

Wednesday, 25 February 2015

Sort container and Array for Dynamics AX

Just a quick snippet for sorting container and Array object.

//Method to sort container
static container sortContainer(container _con)
{
    anytype     temp1;
    anytype     temp2;
    int         i;
    int         j;
    container   sortedCon;
    ;

    sortedCon = _con;

    // Sort the container
    for (i = 1; i <= conlen(sortedCon); i++)
    {
        for (j = i + 1; j <= conlen(sortedCon); j++)
        {
            temp1 = conpeek(sortedCon, j);
            temp2 = conpeek(sortedCon, i);

            if (temp1 < temp2)
            {
                sortedCon = condel(sortedCon, j, 1);
                sortedCon = conins(sortedCon, j, temp2);
                sortedCon = condel(sortedCon, i, 1);
                sortedCon = conins(sortedCon, i, temp1);
            }
        }
    }

    return sortedCon;
}

//Method to sort Array
static Array sortArray(Array _array)
{
    int         i;
    int         arrayCount = _array.lastIndex();
    container   con, sortedCon;

    //Switch from Array to container
    for(i = 1; i <= arrayCount; i++)
    {
        con = conIns(con, i, _array.value(i));
    }
    
    //Sort the container
    sortedCon = sortContainer(con);
    
    //Switch from container to Array
    for(i = 1; i <= conLen(sortedCon); i++)
    {
        _array.value(i, conPeek(sortedCon, i));
    }
    
    return _array;
}

Thursday, 12 February 2015

Copy table buffer between two table with similar structure

Just a quick snippet for those who need it.
The standard buf2Buf() method works if the field Id is identical, but in case the field name is same but have different Id, the code snippet below does the job. It is modified from the buf2Buf() to cater to two tables with similar structure and field name but different field Id.

static void buf2BufByName(
    Common  _from,
    Common  _to
    )
{
    DictTable   dictTableFrom   = new DictTable(_from.TableId);
    DictTable   dictTableTo     = new DictTable(_to.TableId);
    DictField   dictFieldFrom;
    FieldId     fieldIdFrom     = dictTableFrom.fieldNext(0);
    FieldId     fieldIdTo
    ;

    while (fieldIdFrom && ! isSysId(fieldIdFrom))
    {
        dictFieldFrom   = new DictField(_from.TableId, fieldIdFrom);

        if(dictFieldFrom)
        {
            fieldIdTo = dictTableTo.fieldName2Id(dictFieldFrom.name());

            if(fieldIdTo)
                _to.(fieldIdTo) = _from.(fieldIdFrom);
        }

        fieldIdFrom = dictTableFrom.fieldNext(fieldIdFrom);
    }
}