Give every request its own window sums array

The collocator threads accumulated their per window position activation sums
in a global array. This is not a live bug, because a worker processes requests
one after the other, but it is shared mutable state that only works by
accident, and the array was allocated uninitialised. Every request allocates
and zeroes its own array now, so the collocator threads of one request cannot
influence another.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: I9b44ff38871ae2f2729542b8d9f66e489ccc5dd9
diff --git a/lib/IDS/DeReKoVecs/derekovecs-server.c b/lib/IDS/DeReKoVecs/derekovecs-server.c
index e4cc096..9d073a4 100644
--- a/lib/IDS/DeReKoVecs/derekovecs-server.c
+++ b/lib/IDS/DeReKoVecs/derekovecs-server.c
@@ -70,7 +70,6 @@
 } profile_t;
 
 float *M, *M2 = 0L, *syn1neg_window, *expTable;
-float *window_sums;
 char *vocab;
 char *garbage = NULL;
 COLLOCATORDB *cdb = NULL;
@@ -260,11 +259,18 @@
     expTable[i] = exp((i / (float)EXP_TABLE_SIZE * 2 - 1) * MAX_EXP);  // Precompute the exp() table
     expTable[i] = expTable[i] / (expTable[i] + 1);                     // Precompute f(x) = x / (x + 1)
   }
-  window_sums = malloc(sizeof(float) * (window + 1) * 2);
 
   return 0;
 }
 
+/* The collocator threads of one request accumulate their activation sums per
+   window position here. This must not be shared between requests, so every
+   request allocates its own array. Only valid once init_net() has determined
+   the window size, i.e. if M2 is set. */
+float *new_window_sums() {
+  return calloc((window + 1) * 2, sizeof(float));
+}
+
 long mergeVectors(char *file_name) {
   FILE *f;
   int binwords_fd, binvecs_fd;
@@ -782,6 +788,7 @@
 
 SV *getPosWiseW2VCollocators(char *word, long maxPerPos, long cutoff, float threshold, const char *format) {
   float *target_sums = NULL;
+  float *window_sums = NULL;
   long a, b, entries = 0;
   knn *syn_nbs[MAX_THREADS];
   knnpars pars[MAX_THREADS];
@@ -804,6 +811,7 @@
   }
 
   pt = (pthread_t *)malloc((num_threads + 1) * sizeof(pthread_t));
+  window_sums = new_window_sums();
   a = posix_memalign((void **)&target_sums, 128, cutoff * sizeof(float));
   memset(target_sums, 0, cutoff * sizeof(float));
 
@@ -853,6 +861,7 @@
 
   free(result);
   free(target_sums);
+  free(window_sums);
   free(pt);
   free(wl);
   for (a = 0; a < syn_threads; a++) free_knn(syn_nbs[a]);
@@ -871,6 +880,7 @@
 SV *get_neighbours(char *st1, int N, int sort_by, int search_backw, long cutoff, int dedupe, int no_similar_profiles) {
   HV *result = newHV();
   float *target_sums = NULL;
+  float *window_sums = NULL;
   long a, b, c, d, slice;
   knn *para_nbs[MAX_THREADS];
   knn *syn_nbs[MAX_THREADS];
@@ -919,6 +929,7 @@
     pthread_create(&pt[a], NULL, _get_neighbours, (void *)&pars[a]);
   }
   if (M2) {
+    window_sums = new_window_sums();
     for (a = 0; a < syn_threads; a++) {
       pars[a + para_threads].cutoff = cutoff;
       pars[a + para_threads].target_sums = target_sums;
@@ -1207,6 +1218,7 @@
 end:
   free(best);
   free(target_sums);
+  free(window_sums);
   free(pt);
   free(wl);
   for (a = 0; a < MAX_THREADS; a++) {