What is 1280 (0x00000500)

 
Previous Next
ERROR_RECOVERY_FAILURE ERROR_ALREADY_THREAD

ERROR_ALREADY_FIBER

ConvertThreadToFiberEx/ConvertThreadToFiber fails with ERROR_ALREADY_FIBER (1280) if current thread has already been converted to a fiber.

A fiber is a unit of execution that must be manually scheduled by the application. Fibers run in the context of the threads that schedule them. Each thread can schedule multiple fibers. In general, fibers do not provide advantages over a well-designed multithreaded application. However, using fibers can make it easier to port applications that were designed to schedule their own threads.

From a system standpoint, a fiber assumes the identity of the thread that runs it. For example, if a fiber accesses thread local storage (TLS), it is accessing the thread local storage of the thread that is running it. In addition, if a fiber calls the ExitThread function, the thread that is running it exits. However, a fiber does not have all the same state information associated with it as that associated with a thread. The only state information maintained for a fiber is its stack, a subset of its registers, and the fiber data provided during fiber creation. The saved registers are the set of registers typically preserved across a function call.

Fibers are not preemptively scheduled. You schedule a fiber by switching to it from another fiber. The system still schedules threads to run. When a thread running fibers is preempted, its currently running fiber is preempted but remains selected. The selected fiber runs when its thread runs.

Working with Fibers

The first thing to note is that the Windows kernel implements threads. The operating system has intimate knowledge of threads and schedules them according to the algorithm defined by Microsoft. A fiber is implemented in user-mode code; the kernel does not have knowledge of fibers, and they are scheduled according to the algorithm you define. Because you define the fiber-scheduling algorithm, fibers are nonpreemptively scheduled as far as the kernel is concerned.

The next thing to be aware of is that a single thread can contain one or more fibers. As far as the kernel is concerned, a thread is preemptively scheduled and is executing code. However, the thread executes one fiber's code at a time—you decide which fiber. (These concepts will become clearer as we go on).

The first step you must perform when you use fibers is to turn your existing thread into a fiber. You do this by calling ConvertThreadToFiber:

PVOID ConvertThreadToFiber(PVOID pvParam); 

This function allocates memory (about 200 bytes) for the fiber's execution context. This execution context consists of the following elements:

  • A user-defined value that is initialized to the value passed to ConvertThreadToFiber's pvParam argument
  • The head of a structured exception handling chain
  • The top and bottom memory addresses of the fiber's stack (When you convert a thread to a fiber, this is also the thread's stack.)
  • Various CPU registers, including a stack pointer, an instruction pointer, and others

After you allocate and initialize the fiber execution context, you associate the address of the execution context with the thread. The thread has been converted to a fiber, and the fiber is running on this thread. ConvertThreadToFiber actually returns the memory address of the fiber's execution context. You need to use this address later, but you should never read from or write to the execution context data yourself—the fiber functions manipulate the contents of the structure for you when necessary. Now if your fiber (thread) returns or calls ExitThread, the fiber and thread both die.

There is no reason to convert a thread to a fiber unless you plan to create additional fibers to run on the same thread. To create another fiber, the thread (currently running fiber) calls CreateFiber:

 PVOID CreateFiber( DWORD dwStackSize, PFIBER_START_ROUTINE pfnStartAddress, PVOID pvParam); 

CreateFiber first attempts to create a new stack whose size is indicated by the dwStackSize parameter. Usually 0 is passed, which, by default, creates a stack that can grow to 1 MB in size but initially has two pages of storage committed to it. If you specify a nonzero size, a stack is reserved and committed using the specified size.

Next, CreateFiber allocates a new fiber execution context structure and initializes it. The user-defined value is set to the value passed to CreateFiber's pvParam, the top and bottom memory addresses of the new stack are saved, and the memory address of the fiber function (passed as the pfnStartAddress argument) is saved.

The pfnStartAddress argument specifies the address of a fiber routine that you must implement and that must have the following prototype:

 VOID WINAPI FiberFunc(PVOID pvParam); 

When the fiber is scheduled for the first time, this function executes and is passed the pvParam value that was originally passed to CreateFiber. You can do whatever you like in this fiber function. However, the function is prototyped as returning VOID—not because the return value has no meaning, but because this function should never return at all! If a fiber function does return, the thread and all the fibers created on it are destroyed immediately.

Like ConvertThreadToFiber, CreateFiber returns the memory address of the fiber's execution context. However, unlike ConvertThreadToFiber, this new fiber does not execute because the currently running fiber is still executing. Only one fiber at a time can execute on a single thread. To make the new fiber execute, you call SwitchToFiber:

VOID SwitchToFiber(PVOID pvFiberExecutionContext); 

SwitchToFiber takes a single parameter, pvFiberExecutionContext, which is the memory address of a fiber's execution context as returned by a previous call to ConvertThreadToFiber or CreateFiber. This memory address tells the function which fiber to schedule. Internally, SwitchToFiber performs the following steps:

  • It saves some of the current CPU registers, including the instruction pointer register and the stack pointer register, in the currently running fiber's execution context.
  • It loads the registers previously saved in the soon-to-be-running fiber's execution context into the CPU registers. These registers include the stack pointer register so that this fiber's stack is used when the thread continues execution.
  • It associates the fiber's execution context with the thread; the thread runs the specified fiber.
  • It sets the thread's instruction pointer to the saved instruction pointer. The thread (fiber) continues execution where this fiber last executed.

SwitchToFiber is the only way for a fiber to get any CPU time. Because your code must explicitly call SwitchToFiber at the appropriate times, you are in complete control of the fiber scheduling. Keep in mind that fiber scheduling has nothing to do with thread scheduling. The thread that the fibers run on can always be preempted by the operating system. When the thread is scheduled, the currently selected fiber runs—no other fiber runs unless SwitchToFiber is explicitly called.