General

General API Design, Error Handling

Nearly all C API functions in fluxEngine’s API return an int to indicate whether an error has occurred or not. A return value 0 indicates that the function call was successful, while a return value -1 indicates that the function call failed. There are some methods that return a simple number, such as fluxEngine_C_v1_ProcessingContext_num_output_sinks. In that case a non-negative return value indicates that the function call was successful (though the return value may be 0 to indicate that the number is zero), while a value -1 still indicates an error condition.

Error Handling

All API functions that can return an error will have their final parameter be fluxEngine_C_v1_Error** error. This allows the user to specify a pointer to the opaque fluxEngine_C_v1_Error structure to give some indication about the error that occurred:

1
2
3
4
5
6
7
8
 fluxEngine_C_v1_Error* error = NULL;
 int rc = fluxEngine_C_v1_some_function(some parameters, &error);
 if (rc < 0) {
     printf("fluxEngine error: %s\n",
            fluxEngine_C_v1_Error_get_message(error));
     fluxEngine_C_v1_Error_free(error);
     return;
 }

If the user supplies a pointer to capture the error information, they must free it with fluxEngine_C_v1_Error_free, or resources will leak. However, if an API function returns 0 it is guaranteed that the error pointer was not changed and nothing was allocated.

If the user specifies NULL for the error pointer, they can still detect that a function call failed, but they cannot determine by what reason:

1
2
3
4
5
 int rc = fluxEngine_C_v1_some_function(some parameters, NULL);
 if (rc < 0) {
     printf("fluxEngine error (unknown)\n");
     return;
 }

See the documentation of fluxEngine_C_v1_Error for further details on what information may be extracted in the case of an error.

Complex Return Values

There are API functions that create a pointer to an internal structure within fluxEngine. These return that pointer in their penultimate parameter. Take, for example, the fluxEngine_C_v1_init function that initializes fluxEngine. It has the following signature:

int fluxEngine_C_v1_init(void const* license_data, size_t license_data_size, fluxEngine_C_v1_Handle** handle, fluxEngine_C_v1_Error** error)

This follows the standard error handling pattern described above. Additionally it has another output parameter handle that will be filled with the pointer to the newly allocated handle on success.

This may be called in the following manner:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 fluxEngine_C_v1_Error* error = NULL;
 fluxEngine_C_v1_Handle* handle = NULL;
 int rc = fluxEngine_C_v1_init(license_data, license_data_size, &handle, &error);
 if (rc < 0) {
     printf("fluxEngine error: %s\n",
            fluxEngine_C_v1_Error_get_message(error));
     fluxEngine_C_v1_Error_free(error);
     return;
 }
 // From this point on handle contains a valid pointer
 // At the end: free the handle
 fluxEngine_C_v1_destroy(handle);

Initializing the Library

To initialize the library a license file is required. The user must read that license file into memory and supply fluxEngine with it. See read_file for example code for reading the license.

The following code demonstrates how to properly initialize fluxLicense and how to tear it down again.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 fluxEngine_C_v1_Error* error = NULL;
 fluxEngine_C_v1_Handle* handle = NULL;
 int rc = 0;
 void* license_data = NULL;
 size_t license_data_size = 0;

 /* Read the entire license data from disk
  * (see the helper code in the documentation for a possible
  * implementation of this method)
  */
 license_data = read_file("fluxEngine.lic", &license_data_size);
 if (!license_data) {
     fprintf(stderr, "Could not read license file: %s\n", strerror(errno));
     return;
 }

 rc = fluxEngine_C_v1_init(license_data, license_data_size,
                            &handle, &error);
 /* Once fluxLicense has been initialized, we do not need
  * the license data anymore. */
 free(license_data);
 if (rc != 0) {
     fprintf(stderr, "Could not initialize fluxEngine: %s\n",
             fluxEngine_C_v1_Error_get_message(error));
     fluxEngine_C_v1_Error_free(error);
     return;
 }

 /*
  ******************************************************************
  * Do something with fluxLicense here.
  ******************************************************************
  */

 /* At the end: free resources */
 fluxEngine_C_v1_destroy(handle);

Setting up processing threads

fluxEngine supports parallel processing, but it has to be set up at the very beginning. The eastiest method to do so is via the fluxEngine_C_v1_create_processing_threads function.

The following example code demonstrates how to perform processing with 4 threads:

1
2
3
4
5
6
7
8
9
 fluxEngine_C_v1_Error* error = NULL;
 int rc = 0;
 rc = fluxEngine_C_v1_create_processing_threads(handle, 4, &error);
 if (rc != 0) {
     fprintf(stderr, "Could not setup fluxEngine processing threads: %s\n",
             fluxEngine_C_v1_Error_get_message(error));
     fluxEngine_C_v1_Error_free(error);
     return;
 }

Note

This will only create 3 (not 4!) background threads that will help with data processing. The thread that calls fluxEngine_C_v1_ProcessingContext_process_next will be considered the first thread (with index 0) that participates in parallel processing.

Note

Modern processors support Hyperthreading (Intel) or SMT (AMD) to provide more logical cores that are phyiscally available. It is generally not recommended to use more threads than are phyiscally available, as workloads such as fluxEngine will typically slow down when using more cores in a system than are physically available.

Note

When running fluxEngine with very small amounts of data, in the extreme case with cubes that have only one pixel, parallelization will not improve performance. In cases where cubes consisting of only one pixel are processed, it is recommended to not parallelize at all and skip this step.

Note

Only one fluxEngine operation may be performed per handle at the same time; executing multiple processing contexts from different threads will cause them to be run sequentially.

Since it is currently possible to only create a single handle for fluxEngine, this means only one operation can be active at the same time; though the limitation of only a single handle will be lifted in a later version of fluxEngine.