MEDIUM: 51d: Enabled multi threaded operation in the 51Degrees module.

The existing threading flag in the 51Degrees API
(FIFTYONEDEGREES_NO_THREADING) has now been mapped to the HAProxy
threading flag (USE_THREAD), and the 51Degrees module code has been made
thread safe.
In Pattern, the cache is now locked with a spin lock from hathreads.h
using a new lable 'OTHER_LOCK'. The workset pool is now created with the
same size as the number of threads to avoid any time waiting on a
worket.
In Hash Trie, the global device offsets structure is only used in single
threaded operation. Multi threaded operation creates a new offsets
structure in each thread.
diff --git a/src/51d.c b/src/51d.c
index a09d139..7017095 100644
--- a/src/51d.c
+++ b/src/51d.c
@@ -4,6 +4,7 @@
 #include <common/chunk.h>
 #include <common/buffer.h>
 #include <common/errors.h>
+#include <common/hathreads.h>
 #include <common/initcall.h>
 #include <types/global.h>
 #include <proto/arg.h>
@@ -23,6 +24,8 @@
 #ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
 static struct lru64_head *_51d_lru_tree = NULL;
 static unsigned long long _51d_lru_seed;
+
+__decl_spinlock(_51d_lru_lock);
 #endif
 
 static struct {
@@ -37,8 +40,10 @@
 #endif
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
 	int32_t *header_offsets; /* offsets to the HTTP header name string. */
+#ifdef FIFTYONEDEGREES_NO_THREADING
 	fiftyoneDegreesDeviceOffsets device_offsets; /* Memory used for device offsets. */
 #endif
+#endif
 	int cache_size;
 } global_51degrees = {
 	.property_separator = ',',
@@ -201,7 +206,9 @@
 	memcpy(cache_entry->area, smp->data.u.str.area, smp->data.u.str.data);
 	cache_entry->area[smp->data.u.str.data] = 0;
 	cache_entry->data = smp->data.u.str.data;
+	HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
 	lru64_commit(lru, cache_entry, domain, 0, _51d_lru_free);
+	HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
 }
 
 /* Retrieves the data from the cache and sets the sample data to this string.
@@ -250,13 +257,19 @@
 #endif
 
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
-static void _51d_set_device_offsets(struct sample *smp)
+static void _51d_init_device_offsets(fiftyoneDegreesDeviceOffsets *offsets) {
+	int i;
+	for (i = 0; i < global_51degrees.data_set.uniqueHttpHeaders.count; i++) {
+		offsets->firstOffset[i].userAgent = NULL;
+	}
+}
+
+static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
 {
 	struct hdr_idx *idx;
 	struct hdr_ctx ctx;
 	const struct http_msg *msg;
 	int index;
-	fiftyoneDegreesDeviceOffsets *offsets = &global_51degrees.device_offsets;
 
 	idx = &smp->strm->txn->hdr_idx;
 	msg = &smp->strm->txn->req;
@@ -306,13 +319,11 @@
 	char *methodName;
 #endif
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
-static void _51d_process_match(const struct arg *args, struct sample *smp)
+static void _51d_process_match(const struct arg *args, struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
 {
 	char valuesBuffer[1024];
 	const char **requiredProperties = fiftyoneDegreesGetRequiredPropertiesNames(&global_51degrees.data_set);
 	int requiredPropertiesCount = fiftyoneDegreesGetRequiredPropertiesCount(&global_51degrees.data_set);
-	fiftyoneDegreesDeviceOffsets *deviceOffsets = &global_51degrees.device_offsets;
-
 #endif
 
 	char no_data[] = "NoData";  /* response when no data could be found */
@@ -362,7 +373,7 @@
 		for (j = 0; j < requiredPropertiesCount; j++) {
 			property_name = requiredProperties[j];
 			if (strcmp(property_name, args[i].data.str.area) == 0 &&
-				fiftyoneDegreesGetValueFromOffsets(&global_51degrees.data_set, deviceOffsets, j, valuesBuffer, 1024) > 0) {
+				fiftyoneDegreesGetValueFromOffsets(&global_51degrees.data_set, offsets, j, valuesBuffer, 1024) > 0) {
 				found = 1;
 				chunk_appendf(temp, "%s", valuesBuffer);
 				break;
@@ -392,6 +403,9 @@
 	fiftyoneDegreesWorkset* ws; /* workset for detection */
 	struct lru64 *lru = NULL;
 #endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+	fiftyoneDegreesDeviceOffsets *offsets; /* Offsets for detection */
+#endif
 
 	/* Needed to ensure that the HTTP message has been fully received when
 	 * used with TCP operation. Not required for HTTP operation.
@@ -421,13 +435,18 @@
 
 	/* Check the cache to see if there's results for these headers already. */
 	if (_51d_lru_tree) {
+		HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
+
 		lru = lru64_get(_51d_req_hash(args, ws),
 		                _51d_lru_tree, (void*)args, 0);
+
 		if (lru && lru->domain) {
 			fiftyoneDegreesWorksetPoolRelease(global_51degrees.pool, ws);
 			_51d_retrieve_cache_entry(smp, lru);
+			HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
 			return 1;
 		}
+		HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
 	}
 
 	fiftyoneDegreesMatchForHttpHeaders(ws);
@@ -437,15 +456,25 @@
 #endif
 
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifndef FIFTYONEDEGREES_NO_THREADING
+	offsets = fiftyoneDegreesCreateDeviceOffsets(&global_51degrees.data_set);
+	_51d_init_device_offsets(offsets);
+#else
+	offsets = &global_51degrees.device_offsets;
+#endif
 
 	/* Trie is very fast so all the headers can be passed in and the result
 	 * returned faster than the hashing algorithm process.
 	 */
-	_51d_set_device_offsets(smp);
-	_51d_process_match(args, smp);
+	_51d_set_device_offsets(smp, offsets);
+	_51d_process_match(args, smp, offsets);
 
+#ifndef FIFTYONEDEGREES_NO_THREADING
+	fiftyoneDegreesFreeDeviceOffsets(offsets);
 #endif
 
+#endif
+
 #ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
 	fiftyoneDegreesWorksetPoolRelease(global_51degrees.pool, ws);
 	if (lru)
@@ -461,7 +490,9 @@
 	fiftyoneDegreesWorkset* ws; /* workset for detection */
 	struct lru64 *lru = NULL;
 #endif
-
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+	fiftyoneDegreesDeviceOffsets *offsets; /* Offsets for detection */
+#endif
 	/* Flags the sample to show it uses constant memory*/
 	smp->flags |= SMP_F_CONST;
 
@@ -471,12 +502,15 @@
 	if (_51d_lru_tree) {
 		unsigned long long seed = _51d_lru_seed ^ (long)args;
 
+		HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
 		lru = lru64_get(XXH64(smp->data.u.str.area, smp->data.u.str.data, seed),
 		                _51d_lru_tree, (void*)args, 0);
 		if (lru && lru->domain) {
 			_51d_retrieve_cache_entry(smp, lru);
+			HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
 			return 1;
 		}
+		HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
 	}
 
 	/* Create workset. This will later contain detection results. */
@@ -497,10 +531,17 @@
 	_51d_process_match(args, smp, ws);
 #endif
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
-	global_51degrees.device_offsets.firstOffset->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set,
-												   smp->data.u.str.area);
-	global_51degrees.device_offsets.size = 1;
-	_51d_process_match(args, smp);
+#ifndef FIFTYONEDEGREES_NO_THREADING
+	offsets = fiftyoneDegreesCreateDeviceOffsets(&global_51degrees.data_set);
+	_51d_init_device_offsets(offsets);
+#else
+	offsets = &global_51degrees.device_offsets;
+#endif
+
+	offsets->firstOffset->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set,
+	                                                                    smp->data.u.str.area);
+	offsets->size = 1;
+	_51d_process_match(args, smp, offsets);
 #endif
 
 #ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
@@ -509,6 +550,12 @@
 		_51d_insert_cache_entry(smp, lru, (void*)args);
 #endif
 
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifndef FIFTYONEDEGREES_NO_THREADING
+	fiftyoneDegreesFreeDeviceOffsets(offsets);
+#endif
+#endif
+
 	return 1;
 }
 
@@ -535,8 +582,11 @@
 	int index = 0;
 	fiftyoneDegreesDataSet *ds = &global_51degrees.data_set;
 	global_51degrees.header_count = fiftyoneDegreesGetHttpHeaderCount(ds);
+#ifdef FIFTYONEDEGREES_NO_THREADING
 	global_51degrees.device_offsets.firstOffset = malloc(
 		global_51degrees.header_count * sizeof(fiftyoneDegreesDeviceOffset));
+	_51d_init_device_offsets(&global_51degrees.device_offsets);
+#endif
 	global_51degrees.header_names = malloc(global_51degrees.header_count * sizeof(struct buffer));
 	global_51degrees.header_offsets = malloc(global_51degrees.header_count * sizeof(int32_t));
 	for (index = 0; index < global_51degrees.header_count; index++) {
@@ -562,8 +612,8 @@
 	if (!global_51degrees.data_file_path)
 		return 0;
 
-	if (global.nbthread > 1) {
-		ha_alert("51Degrees: multithreading is not supported for now.\n");
+	if (global.nbthread < 1) {
+		ha_alert("51Degrees: The thread count cannot be zero or negative.\n");
 		return (ERR_FATAL | ERR_ALERT);
 	}
 
@@ -586,10 +636,7 @@
 	switch (_51d_dataset_status) {
 		case DATA_SET_INIT_STATUS_SUCCESS:
 #ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
-			/* only 1 workset in the pool because HAProxy is currently single threaded
-			 * this value should be set to the number of threads in future versions.
-			 */
-			global_51degrees.pool = fiftyoneDegreesWorksetPoolCreate(&global_51degrees.data_set, NULL, 1);
+			global_51degrees.pool = fiftyoneDegreesWorksetPoolCreate(&global_51degrees.data_set, NULL, global.nbthread);
 #endif
 			_51d_init_http_headers();
 			break;
@@ -658,7 +705,9 @@
 	fiftyoneDegreesWorksetPoolFree(global_51degrees.pool);
 #endif
 #ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifdef FIFTYONEDEGREES_NO_THREADING
 	free(global_51degrees.device_offsets.firstOffset);
+#endif
 	free(global_51degrees.header_offsets);
 #endif
 	fiftyoneDegreesDataSetFree(&global_51degrees.data_set);