Skip to content

Commit

Permalink
Thread-safe implementatino of ag_search()
Browse files Browse the repository at this point in the history
This commit adds a 'thread-safe' version of ag_search(). Note that the
implementation is as rudimentary as possible and serializes all calls
to ag_search(). Parallel calls to ag_search_ts() will not be faster.

Part of this limitation concerns how ag is structured. See the issues
on GitHub for more information.

Note, however, that ag_search()/ag_search_ts() stays parallel for
multiple paths in a single call, so I would recommend using a single
call to multiples files instead of multiple calls whenever possible.
  • Loading branch information
Theldus committed Jun 12, 2021
1 parent c280261 commit 7807763
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 7 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ uninstall:
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_init.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_init_config.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_search.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_search_ts.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_set_config.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_start_workers.3
$(Q)rm -f $(DESTDIR)$(MANDIR)/man3/ag_stop_workers.3
Expand Down
1 change: 1 addition & 0 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Those that differ are listed in the table below:
| Function | C | Python equivalent |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **ag_search** | Parameters:<br>query (**char \***), npaths (**int**), target_paths (**char\*\***), nresults (**size_t\***)<br><br>Return:<br>On success: **struct ag_result \*\***, nresults (**size_t\***)<br>On error: **null**, 0 | Parameters:<br>query (**string**), target_paths (list of strings)<br><br>Return:<br>On success: tuple of (nresults (integer), tuple(**ag_result**))<br>On error: tuple of (0, **Py_None**) |
| **ag_search_ts** | Same as **ag_search** | |
| **ag_free_all_results** | Parameters:<br>results (**struct ag_result \*\***), nresults (**size_t**)<br><br>Return: nothing | Parameters:<br>tuple(**ag_result**)<br><br>Return: nothing |

Please note that although Python has a garbage collector, the memory allocated
Expand Down
6 changes: 4 additions & 2 deletions doc/man3/ag_search.3
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
.\" See the License for the specific language governing permissions and
.\" limitations under the License.
.\"
.TH man 3 "29 May 2021" "1.0" "libag man page"
.TH man 3 "11 June 2021" "1.0" "libag man page"
.SH NAME
ag_search \- Searches for a given pattern on a given path
.SH SYNOPSIS
Expand Down Expand Up @@ -52,7 +52,8 @@ is set to zero.

.SH NOTES
Please note that this routine is _not_ thread-safe, and should not be called
from multiples threads.
from multiples threads. For a thread-safe version, please check
.BR ag_search_ts ().

The current behavior of
.BR ag_search ()
Expand Down Expand Up @@ -104,6 +105,7 @@ int main(void) {
}

.SH SEE ALSO
.BR ag_search_ts (3),
.BR ag_free_result (3),
.BR ag_free_all_results (3),
.BR ag_init_config (3),
Expand Down
74 changes: 74 additions & 0 deletions doc/man3/ag_search_ts.3
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.\"
.\" Copyright 2021 Davidson Francis <[email protected]>
.\"
.\" Licensed under the Apache License, Version 2.0 (the "License");
.\" you may not use this file except in compliance with the License.
.\" You may obtain a copy of the License at
.\"
.\" http://www.apache.org/licenses/LICENSE-2.0
.\"
.\" Unless required by applicable law or agreed to in writing, software
.\" distributed under the License is distributed on an "AS IS" BASIS,
.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.\" See the License for the specific language governing permissions and
.\" limitations under the License.
.\"
.TH man 3 "11 June 2021" "1.0" "libag man page"
.SH NAME
ag_search_ts \- Searches for a given pattern on a given path (thread-safe)
.SH SYNOPSIS
.nf
.B #include <libag.h>
.sp
.BI "struct ag_result **ag_search_ts(char *" query ", int " npaths ,
.BI " char **" target_paths ", size_t *" nresults ");"
.fi
.SH DESCRIPTION
The
.BR ag_search_ts ()
function searches for
.I query
on all paths given in the
.I target_paths
list. The
.I npaths
specify the number of paths (whether files or directories) contained
in
.IR target_paths .
When a successful call to
.BR ag_search ()
is made, the number of results is saved in
.IR nresults .

.SH RETURN VALUE
On success, returns a list of (struct ag_result*) containing all the results
found. It is up to the user to free the results, whether with
.BR ag_free_result ()
or
.BR ag_free_all_results ().
On error, returns NULL and
.I nresults
is set to zero.

.SH NOTES
Please note that this version is the same as
.BR ag_search ()
but thread-safe.

Also note that the thread-safe version here is the most rudimentary
implementation possible: just one lock on the entire function. Which
implies that parallel calls to this function will not be parallel, but
sequential.

A more efficient implementation is needed. See the issues on GitHub for a
breakdown of this.

.SH SEE ALSO
.BR ag_search (3),
.BR ag_free_result (3),
.BR ag_free_all_results (3),
.BR ag_init_config (3),
.BR ag_set_config (3)

.SH AUTHOR
Davidson Francis ([email protected])
71 changes: 66 additions & 5 deletions libag.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ static struct thrd_result
*/
static struct ag_config config;

/*
* @brief Mutex for safe-thread search.
*/
static pthread_mutex_t search_mtx;

/*
* ============================================================================
* Util
Expand Down Expand Up @@ -586,14 +591,16 @@ int ag_start_workers(void)
goto err3;
if (pthread_mutex_init(&work_queue_mtx, NULL))
goto err4;
if (pthread_barrier_init(&worker_done, NULL, workers_len + 1))
if (pthread_mutex_init(&search_mtx, NULL))
goto err5;
if (pthread_barrier_init(&results_done, NULL, workers_len + 1))
if (pthread_barrier_init(&worker_done, NULL, workers_len + 1))
goto err6;
if (pthread_barrier_init(&results_done, NULL, workers_len + 1))
goto err7;

/* Reset per-thread local results. */
if (reset_local_results(1))
goto err7;
goto err8;

/* Start workers and wait for something. */
for (i = 0; i < workers_len; i++)
Expand All @@ -611,10 +618,12 @@ int ag_start_workers(void)
}
}
return (0);
err7:
err8:
pthread_barrier_destroy(&results_done);
err6:
err7:
pthread_barrier_destroy(&worker_done);
err6:
pthread_mutex_destroy(&search_mtx);
err5:
pthread_mutex_destroy(&work_queue_mtx);
err4:
Expand Down Expand Up @@ -657,6 +666,7 @@ int ag_stop_workers(void)
pthread_mutex_destroy(&work_queue_mtx);
pthread_mutex_destroy(&print_mtx);
pthread_mutex_destroy(&stats_mtx);
pthread_mutex_destroy(&search_mtx);
pthread_barrier_destroy(&worker_done);
pthread_barrier_destroy(&results_done);
cleanup_ignore(root_ignores);
Expand Down Expand Up @@ -799,6 +809,57 @@ struct ag_result **ag_search(char *query, int npaths, char **target_paths,
return (result);
}

/**
* @Brief Searches for @p query recursively in all @p target_paths.
*
* This is the same behavior as the @ref ag_search routine, but thread-safe.
*
* @param query Pattern to be searched.
* @param npaths Number of paths to be searched.
* @param target_paths Paths list.
* @param nresults Pointer to number of results found.
*
* @return Returns a list of (struct ag_result*) containing all
* the results found. If nothing found, NULL.
*
* @note Please note that this version is the same as ag_search() but
* thread-safe.
*
* Also note that the thread-safe version here is the most rudimentary
* implementation possible: just one lock on the entire function. Which
* implies that parallel calls to this function will not be parallel, but
* sequential.
*
* A more efficient implementation is needed. See the issues on GitHub for a
* breakdown of this.
*/
struct ag_result **ag_search_ts(char *query, int npaths, char **target_paths,
size_t *nresults)
{
struct ag_result **r;

/* Check if libag was initialized. */
if (!has_ag_init)
return (NULL);

/* Query and valid paths. */
if (!query || !target_paths || !nresults || npaths <= 0)
return (NULL);

/* Check if workers already started or I should start them. */
if (!workers)
{
if (config.workers_behavior != LIBAG_ONSEARCH_WORKERS)
return (NULL);
}

/* Lock search. */
pthread_mutex_lock(&search_mtx);
r = ag_search(query, npaths, target_paths, nresults);
pthread_mutex_unlock(&search_mtx);
return (r);
}

/**
* @brief If stats are enabled, get the current stats for
* the latest @ref ag_search call.
Expand Down
2 changes: 2 additions & 0 deletions libag.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@
extern int ag_finish(void);
extern struct ag_result **ag_search(char *query, int npaths,
char **target_paths, size_t *nresults);
extern struct ag_result **ag_search_ts(char *query, int npaths,
char **target_paths, size_t *nresults);
extern int ag_get_stats(struct ag_search_stats *ret_stats);
extern void ag_free_result(struct ag_result *result);
extern void ag_free_all_results(struct ag_result **results,
Expand Down

0 comments on commit 7807763

Please sign in to comment.