[Compuware Corporation] [Compuware NuMega home page]                [NuMega Lab]
[teal]

 [DriverStudio]    [Image][Image]
   Home                       [Image]
 [Driver Products]                          On Calling IoCancelIrp
   DriverStudio          IoCancelIrp must be used with caution in order to avoid attempts to cancel IRPs
   DriverBundle          that have already been completed and freed. The rules for dealing with this
   Previews              condition depend on how the IRP in question was initialized.
   Compatibility
 [Downloads]             Consider the case where a driver allocates and initializes an IRP, sends it to
 Wizards                  a lower device, and then waits for the IRP to complete. In other words, the
   Utilities             driver expects the lower device to complete the IRP synchronously. The driver
   NT source             waits by calling KeWaitForSingleObject, passing an event object associated with
 examples                 the IRP.
   VxD source
 examples                 The driver may not want to wait indefinitely. Fortunately,
   WDM source            KeWaitForSingleObject allows its caller to specify a timeout period. If the
 examples                 event is not signaled before the timeout period expires, the service returns
 [Resources]             STATUS_TIMEOUT.
 Technical papers         Upon return of STATUS_TIMEOUT, the driver needs to notify the lower device that
   Useful links          the operation has timed out. The best way to do this is to call IoCancelIrp,
   Technical tips        which tells the lower device to cancel the IRP. The problem is that the lower
 [Support]               device may complete the IRP just as the timer expires, and that could result in
 Support                  the IRP being freed or even reused by another device. It is the responsibility
   Knowledge base        of the calling driver to either prevent the completion of the IRP from
   Problem               proceeding to its final deallocation, or to serialize the cancellation with the
 submission               completion operation. The choice depends on how the IRP was created and
   Product               initialized.
 registration
   Release notes         Case 1:
 [Shop NuMega]           IRPs created with IoAllocateIrp
 Buy it!                  If a driver allocates an IRP with IoAllocateIrp, then the driver must set up a
   Price list            completion routine that returns STATUS_MORE_PROCESSING_REQUIRED. When a
   How to buy            completion routine returns this value, the system suspends completion of the
   Sales offices         IRP at the IRP stack location associated with the completion routine. This is
                          required regardless of whether or not the driver ever tries to cancel the IRP,
                          because an IRP initialized by IoAllocateIrp lacks the information that the
 [Y2K Compliance]         system would need to carry out the final stages of IRP completion.

                          In a driver that enforces a timeout period, the completion routine may set the
 [More information]       event on which the calling driver is waiting, but must not call IoFreeIrp. If
                          the completion routine were to free the IRP, the main thread's concurrent call
                          IoCancelIrp could cause a page fault or data corruption. The solution is to
                          require the main thread to regain control of the IRP after the lower device
                          completes or cancels it, by means of a completion routine that prematurely
                          terminates the completion process. Then can the main thread safely free the
                          IRP.

                          Here is a fragment of DriverWorks code to illustrate how this is done:

                          NTSTATUS status;
                          KEvent CompletionEvent(NotificationEvent);
                          KIrp  I(KIrp::Allocate()); // uses IoAllocateIrp
                          I.SetCompletionRoutine(
                                  SynchCompletionRoutine,
                                  &CompletionEvent,
                                  TRUE, TRUE, TRUE);
                          // . . . set up more IRP parameters here . . .
                          LowerDevice.Call(I);

                          timeout.QuadPart = -(50*1000*1000); // 5 seconds
                          if ( CompletionEvent.Wait(KernelMode, TRUE, &timeout) == STATUS_TIMEOUT )
                          {
                                  IoCancelIrp(I);
                                  CompletionEvent.Wait();
                          }

                          status = I.Status();
                          KIrp::Deallocate(I);

                          And here is the completion routine:

                          NTSTATUS SynchCompletionRoutine(PDEVICE_OBJECT pDev, PIRP pIrp, PVOID Ctx)
                          {
                                  KEvent* pEvent = static_cast(Ctx);
                                  pEvent->Set();
                                  return STATUS_MORE_PROCESSING_REQUIRED;
                          }

                          Note that the second call to KEvent::Wait inside the if clause for the timeout
                          is necessary for serialization. By convention, the lower device must respond to
                          the cancellation request in a timely manner, assuming that it has not already
                          completed the IRP. If it has completed the IRP, then the IRP is not in a
                          cancelable state, and setting its cancel flag is harmless. Either way, the
                          completion routine will run and set the event on which the main thread is
                          waiting. If the lower device neither responds to the cancel request nor
                          completes the IRP, the thread is hung.

                          By the way, never call IoCancelIrp while holding the global cancel spin lock.
                          Doing so will cause a deadlock because the system needs to take that lock
                          before calling an IRP's cancel routine.

                          Case 2:
                          IRPs created with IoBuildDeviceIoControlRequest or IoBuildSynchronousFsdRequest

                          IRPs that a driver builds with IoBuildDeviceIoControlRequest or
                          IoBuildSynchronousFsdRequest contain the information that the system needs to
                          carry out the final stage of IRP completion. Specifically, such IRPs are put on
                          a list of IRPs associated with the calling thread. As a result, a driver does
                          not need to set up a completion routine that prematurely terminates completion
                          processing. The system automatically sets the event provided by the driver, and
                          then frees the IRP. The key point is that this final processing is done at
                          IRQL=APC_LEVEL, in the context of the thread that created the IRP.

                          A driver can take advantage of the fact that the final completion processing is
                          done in the original thread at raised IRQL to serialize completion processing
                          and cancellation. Consider this fragment from the DDK's parallel port class
                          driver:

                          KeInitializeEvent(&event, NotificationEvent, FALSE);
                          irp = IoBuildDeviceIoControlRequest(
                                  IOCTL_INTERNAL_PARALLEL_PORT_ALLOCATE,
                                  Extension->PortDeviceObject,
                                  NULL, 0, NULL, 0, TRUE, &event,
                                  &ioStatus);

                          if (!irp)
                                  return;
                          // note that no completion routine has been set up
                          IoCallDriver(Extension->PortDeviceObject, irp);

                          timeout.QuadPart = -(50*1000*1000); // 5 seconds
                          status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, &timeout);

                          if (status == STATUS_TIMEOUT)
                          {
                                  KeRaiseIrql(APC_LEVEL, &oldIrql);

                                  if (KeReadStateEvent(&event) == 0)
                                          IoCancelIrp(irp);
                                  }

                                  KeLowerIrql(oldIrql);
                                  KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
                          }

                          If the request times out, the driver raises IRQL to APC_LEVEL. The system
                          cannot run the APC that performs final completion processing while IRQL is
                          raised. Before calling IoCancelIrp, the driver checks the state of the event in
                          order to determine if the APC ran just prior to raising IRQL. If the event is
                          still not signaled, then it's safe to request cancellation because the IRP
                          cannot have been freed. The driver lowers IRQL before entering the second wait.
                          As above, the thread may hang if the lower device neither cancels nor completes
                          the IRP.

                          It follows that if a driver builds a synchronous IRP with one of the above
                          services, it should wait for the IRP's completion on the same thread.
                          Otherwise, the APC does not ensure serialization on multiprocessor systems.

                          One final note: IRPs built with IoBuildAsynchronousFsdRequest behave as those
                          built with IoAllocateIrp.

  DriverCentral  DriverStudio  Free downloads  Resources  Support and
                          Services  Shop NuMega
     Compuware NuMega  Tel: +1 603 578-8400  Updated: 9 August 1999 
                      Problems? Contact our webmaster.
