Skip to content

Commit 0354a9b

Browse files
committed
run the module body only once per dll
1 parent 173cad0 commit 0354a9b

3 files changed

Lines changed: 35 additions & 11 deletions

File tree

‎ChangeLog‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2026-10-29 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/Module.h (RCPP_MODULE): Run the module body only the
4+
first time the boot function is called, since it adds to the static
5+
module and every Module() call boots the module again, which duplicated
6+
constructors and methods (#1527)
7+
* inst/tinytest/test_module.R: Test loading a module repeatedly
8+
19
2026-10-29 Kevin Ushey <kevinushey@gmail.com>
210

311
* inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot

‎inst/include/Rcpp/Module.h‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -630,17 +630,23 @@ namespace Rcpp {
630630
#define RCPP_MODULE_BOOT(name) _rcpp_module_boot_##name
631631

632632
// the boot function is called from R via .Call(), so exceptions thrown by
633-
// the module body must be turned into R errors
634-
#define RCPP_MODULE(name) \
635-
void _rcpp_module_##name##_init() ; \
636-
static Rcpp::Module _rcpp_module_##name( # name ) ; \
637-
extern "C" SEXP _rcpp_module_boot_##name(){ \
638-
BEGIN_RCPP \
639-
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
640-
_rcpp_module_##name##_init( ) ; \
641-
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
642-
END_RCPP \
643-
} \
633+
// the module body must be turned into R errors; it runs every time the
634+
// module is loaded, but the body adds to the static module, so it may only
635+
// run once
636+
#define RCPP_MODULE(name) \
637+
void _rcpp_module_##name##_init() ; \
638+
static Rcpp::Module _rcpp_module_##name( # name ) ; \
639+
static bool _rcpp_module_##name##_initialized = false ; \
640+
extern "C" SEXP _rcpp_module_boot_##name(){ \
641+
BEGIN_RCPP \
642+
if( ! _rcpp_module_##name##_initialized ){ \
643+
Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \
644+
_rcpp_module_##name##_init( ) ; \
645+
_rcpp_module_##name##_initialized = true ; \
646+
} \
647+
return Rcpp::XPtr<Rcpp::Module>( & _rcpp_module_##name , false ) ; \
648+
END_RCPP \
649+
} \
644650
void _rcpp_module_##name##_init()
645651

646652
// silly little dance to suppress a 'defined but not used variable' warning

‎inst/tinytest/test_module.R‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,13 @@ gctorture(TRUE)
128128
res <- gadget$value()
129129
gctorture(FALSE)
130130
expect_identical( res, c(1, 2, 3) )
131+
132+
## loading a module again must not re-run its body, which used to add
133+
## every constructor and method to the static module once more (#1527);
134+
## this redefines the module's classes, so it comes last
135+
is_demo_module <- function(dll) is.loaded("_rcpp_module_boot_demoModule", PACKAGE = dll[["name"]])
136+
dll <- Filter(is_demo_module, getLoadedDLLs())[[1L]]
137+
mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE )
138+
mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE )
139+
expect_equal( length( mod$ModuleWorld@constructors ), 1L )
140+
expect_equal( length( mod$ModuleWorld@methods$greet@nargs ), 1L )

0 commit comments

Comments
 (0)