AndreasFagschlunger / O2Xfs

Java API for accessing CEN/XFS API, EMV Level 2 Kernel
47 stars 28 forks source link

Canceling Command #73

Closed VitalyEG closed 5 years ago

VitalyEG commented 5 years ago

Hi Andreas. I have a trouble with this core. I have a question about cancelling command. Can I cancel command, when command not complete. as example: We have two CIM device(First - Biills, Second - Coins) and we work with scenario of CIM XFS 3.30 part 8.6 documenttation. When someone device cash in item I check sum and if it's enough I need to complete cash in for the second device. I use XfsCommand.cancel():

public void cancel() throws XfsException { synchronized (this) { if (requestId != null) { XfsServiceManager.getInstance().cancel(xfsService, requestId); } } }

and it has locked at synchronized command.

AndreasFagschlunger commented 5 years ago

Hi @VitalyEG Didn't get your message on my phone, sorry for the late reply. You can send a request for cancellation as soon as you called execute. But the service provider decides if it will honor your request or not. Check out ReadCardCommand.java and IDCReadTask.java.

IDCReadTask asks you to insert a card or displays a cancel button to stop waiting for a card, which leads to calling ReadCardCommand.cancel(). But keep in mind, it just sends a request to cancel the command to the Service provider. The command may return with ERR_CANCELED, but it may also ignore it completely. Only thing you can rely on is, that there is only one outcome.

With best regards, Andreas Fagschlunger

VitalyEG commented 5 years ago

Hi, Andreas! Thanks for this answer. I was studing this question for several days. I'll test work of one and more than one XFS CIM devices and I'll let to know you about results.

VitalyEG commented 5 years ago

Hi Andreas! If the result of the service provider is not sent immediately, and it's send after sending SRVE, EXEE messages, command is considered not completed and it's cancellation can not possible (CIM XFS 3.30 part 8.6).

Cancellation is possible only for the completed commands, which returned the result. So this unfinished asynchronous command blocks asynchronous information commands and they are queued.

So I can use only timeout from the service provider, indicating the expiration time of my command. Then the service provider decides to send the cancellation (throw onError TIMEOUT(-48)) or ignore it.

VitalyEG commented 5 years ago

Parhaps, If I''ll be use script 8.1, I''ll have no problems.

VitalyEG commented 5 years ago

Could you told me difference between AbstractAsyncCommand and AbstractAsyncXfsCommand?

Shroudedd commented 5 years ago

Do different vendors require different approaches and code? Or is it possible to speak directly to xfs so it would work on any machine?

On Mon, Nov 26, 2018, 11:14 AM VitalyEG <notifications@github.com wrote:

Could you told me difference between AbstractAsyncCommand and AbstractAsyncXfsCommand?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/AndreasFagschlunger/O2Xfs/issues/73#issuecomment-441761325, or mute the thread https://github.com/notifications/unsubscribe-auth/AjaaFyEwaAHcMmKJvILLeHvskXb7lU4vks5uzD2vgaJpZM4YllnL .

VitalyEG commented 5 years ago

The XFS should work identically at all machines, if these vendors follow the rules of scenarios and device compatibility structure (CAPABILIPES). But really, different vendors understand the description of the standard in different ways.

VitalyEG commented 5 years ago

I understood what's the problem. Since according to the scenario, the answer does not come, and answer come only after insertion, then it is not possible to enter the cancel command from this kernel.

VitalyEG commented 5 years ago

8.6 OK Transaction (Implicit Shutter Control)

XFS Commands and Events

  1. Customer selects cash-in operation. WFS_CMD_CIM_CASH_IN_START
    • WFS_CMD_CIM_CASH_IN initiated The Service Provider implicitly opens the shutter. … WFS_SRVE_CIM_SHUTTERSTATUS-CHANGED(WFS_CIM_SHTOPEN) WFS_EXEE_CIM_INSERTITEMS event is sent when the shutter is fully open and the device is ready to begin accepting items.
  2. Ask the customer to insert money.
  3. Customer inserts money.
  4. If bItemsInsertedSensor == TRUE: WFS_SRVE_CIM_ITEMSINSERTED The Service Provider implicitly closes the shutter. … WFS_SRVE_CIM_SHUTTERSTATUS-CHANGED(WFS_CIM_SHTCLOSED) The bill recognition begins.
    • WFS_CMD_CIM_CASH_IN command completes.
VitalyEG commented 5 years ago

I can get result only at p.6. Could you help me Andreas?

AndreasFagschlunger commented 5 years ago

Hello @VitalyEG Sorry for the late reply, 24 hours per day don't seem to be enough these days. To be honest, I don't fully understand the problem yet.

As I understand it, you want the customer to insert a specific amount of bills and a specific amount of coins? You start both Cash-In-Transactions simultaneously, so the customer can insert bills and coins at the same time?

As I see it, you just wait until both CASH_IN-Commands end. If the amount is high enough, you finish both Cash-In-Transactions by executing CASH_IN_END-Command. If the money isn't enough, you execute CASH_IN-Command again and ask for more. At least, if the customer doesn't provide enough money, you execute CASH_IN_ROLLBACK commands and the customer gets his money back.

In this scenario I expect both devices do have a intermediate stacker (at.o2xfs.xfs.v3_00.cim.Capabilities3.getIntermediateStacker() > 0).

VitalyEG commented 5 years ago

Hi Andreas. I'm sorry, I did not describe in detail the process. When the user presses the pay button, I activate the 2 devices with used the CASH_IN commands to accept units, Devices BILLS and COINS work with cash in at the same time. Depending on which nominal will come first, the service provider to this CASH_IN will return onItemsInserted answer about the accepted nominal and onComplete with the structure of the accepted nominal values. But the CASH_IN command of the second device does not return the result (the result of wfsAsyncExecute does not come to me), because it's waiting for the result and I can't cancel command that not complete.

Maybe I or service provider don't understand each other. Maybe service provider need to send me intermediate result at command CASH_IN and I could be use cancel.

AndreasFagschlunger commented 5 years ago

You can for sure cancel an ongoing command, e.g. if the customer doesn't insert money, you have to cancel the CASH_IN commands.

When I understand you correctly, you want to cancel one CASH_IN command as soon as the first one completes? If you call cancel in at.o2xfs.xfs.service.cmd.event.CommandListener.onComplete(T) directly, you may have a dead lock. You shouln't execute XFS commands while still processing XFS events, try to move those things in a new thread and keep time spend in the CommandListener as little as possible. Currently there is only one thread who delivers XFS events.

Here is a code snippted to explain my intention:

public class CashInDemo implements CashInListener {

    private CancelEvent cancelEvent;
    private ErrorEvent errorEvent;
    private CashInCompleteEvent completeEvent;

    private void doRun() throws InterruptedException {
        CimService cimService = null;
        CashInCommand command = new CashInCommand(cimService);
        synchronized (this) {
            command.execute();
            while (cancelEvent == null && errorEvent == null && completeEvent == null) {
                wait();
            }
        }
        if (cancelEvent != null) {
            // Command has been cancelled
        } else if (errorEvent != null) {
            // Command failed
        } else {
            // Successful, safe to start a new command or cancel existings commands.
        }
    }

    @Override
    public void onCancel(CancelEvent event) {
        synchronized (this) {
            cancelEvent = event;
            notifyAll();
        }
    }

    @Override
    public void onError(ErrorEvent event) {
        synchronized (this) {
            errorEvent = event;
            notifyAll();
        }
    }

    @Override
    public void onComplete(CashInCompleteEvent event) {
        synchronized (this) {
            completeEvent = event;
            notifyAll();
        }
        // Don't execute XFS commands here
    }

    public static void main(String[] args) throws InterruptedException {
        new CashInDemo().doRun();
    }
}
VitalyEG commented 5 years ago

When I understand you correctly, you want to cancel one CASH_IN command as soon as the first one completes? Or not completes. Maybe trouble at side service provider.

I use cancel on closing Task in method: @Override protected void close() { if (cashInCommand != null) { cashInCommand.cancel(); } ... }

When I start CASH_IN command, this method starts. public void execute(final XfsEventNotification xfsEventNotification) throws XfsException { String method = "execute"; synchronized (this) { if (requestId != null) { throw new IllegalStateException("already executed"); } requestId = XfsServiceManager.getInstance().execute(this, xfsEventNotification); LOG.info(method, "IN"); } } And I don't get requestId. I'm await result from service provider. And I''ll get requestId only when item will be inserted. So I can't cancel command.

public void cancel() throws XfsException { String method = "cancel()"; LOG.info(method, "IN"); synchronized (this) { if (requestId != null) { XfsServiceManager.getInstance().cancel(xfsService, requestId); } } }

And Another question: ...Currently there is only one thread who delivers XFS events... I read that xfs store async commands into FIFO queue. But I didn't see how he store for one device or all devices?

AndreasFagschlunger commented 5 years ago

You usually don't get the request ID directly, it's stored in the XfsCommand class. And the request ID assigned as soon as execute returns, so at the very beginning of the command.

So this should basically work:

CashInCommand command = ...
command.execute();
command.cancel();

In the O²Xfs Framework there is currently only one thread dispatching the XFS events. It may be possible to define a dispatch thread per service, but dispatching every event in a different thread may cause execute events to arrive after complete events and so on.

So back to your problem, I can't pinpoint the exact problem. As I understand it you call cancel and the command doesn't cancel. The question is, does or doesn't arrive the cancel request at the service provider. If it this you should see it in the log files, you may also check the XFS Manager log.

If the O²Xfs fails to deliver the cancel request, it may be a lock issue. So you schedule a cancel request while still locking the request queue. You can try to explicitly create a new Thread and call the cancel-Method there.

If the cancel request does arrive at the service provider, it may be a issue with the service provider.

Last thing you can do is provide a bit of code to reproduce the problem.

VitalyEG commented 5 years ago

The cancel request doesn't arrive at the service provider.

Here is an example:

public class CimPayTask extends TerminalXfsServiceTask<CimService> implements CashInListener {

  private static final Logger LOG = LoggerFactory.getLogger(CimPayTask.class);
  private Object lockCashIn = new Object();
  private CashInCommand cashInCommand = null;

  public CimPayTask(...) throws Exception {
    ...
  }

  @Override
  protected void execute() {
    String method = "execute()";
    try {
      cashInCommand = new CashInCommand(cimService);
      cashInCommand.addCommandListener(this);
      cashInCommand.execute();
      LOG.info(method, "After CashInCommand execute");
      synchronized (lockCashIn) {
        lockCashIn.wait();
      }

    } catch (Exception e) {
      ...
    }
  }

  @Override
  public void onCancel(CancelEvent event) {
    String method = "onCancel";
    LOG.info(method, "Thread CimPayTask==>" + Thread.currentThread().getId());
    synchronized (lockCashIn) {
      lockCashIn.notify();
    }
  }

  @Override
  public void onError(ErrorEvent event) {
    String method = "onError";
    LOG.error("PayCimTask.onError:", event, event.getException());
    synchronized (lockCashIn) {
      lockCashIn.notify();
    }
  }

  @Override
  public void onComplete(CashInCompleteEvent event) {

    String method = "onComplete";
    LOG.info(method, "Thread CimPayTask onComplete==>" + Thread.currentThread().getId());
    if (cashInCommand != null) {
      cashInCommand.removeCommandListener(this);
      cashInCommand = null;
    }
    synchronized (lockCashIn) {
      lockCashIn.notify();
    }
    LOG.info(method, "END ON COMPLETE");
  }

  @Override
  protected void close() {
    String method = "close";
    try {
      LOG.info(method, "close start");
      if (cashInCommand != null) {
        cashInCommand.cancel();
        /*CancelCommandTask cancelCommandTask = new CancelCommandTask(cimDevice, cashInCommand);
        taskManager.executeSubTask(cancelCommandTask, true);*/
        synchronized (lockCashIn) {
          lockCashIn.notify();
        }
      }
      LOG.info(method, "close end");
    } catch (Exception e) {
      LOG.error(method, "ERROR CASH_IN: ", e);
    }
  }

}
AndreasFagschlunger commented 5 years ago

You should always wait on behalf of a condition not met:

      synchronized (lockCashIn) {
        lockCashIn.wait();
      }

vs.

        synchronized (lockCashIn) {
            ...
            while (cancelEvent == null && errorEvent == null && completeEvent == null) {
                lockCashIn.wait();
            }
        }

Also use notifyAll instead of notify, to ensure every waiting thread is notified.

And in which scenario does the problem occur? The said the command doens't get cancelled, so you have a button which calls close and the command doesn't cancel?

VitalyEG commented 5 years ago

Hi Andreas! Thank you very much! The trouble was at the side of service provider. It can't cancel operation. I have no another XFS-libraries,at this time, that I can test with cancel command. But if I'll get other XFS-libraries, I'll write you. How do you test cancel command? Which libraries do you have? Do you have C++sources with example cancellation command of service provider side?

AndreasFagschlunger commented 5 years ago

I didn't do any CIM operations yet, but I'm currently working on a service provider interface (SPI) for development. Basically you can simulate then without any hardware a CIM devices which reacts as you wish to cancel commands.

Beside that, the card reading etc. was tested on actual hardware, so the cancel command as it is should work fine.

VitalyEG commented 5 years ago

Thanks, Andreas! I'm glad to hear it. You can close this issue. I have a question but it'll be in another "issue". Thanks!