Are you sure you got error
"Maximum number of GUI sessions reached"
due to the function call?
CALL FUNCTION 'BAPI_MATERIAL_SAVEREPLICA'
STARTING NEW TASK lv_taskname
DESTINATION IN GROUP DEFAULT
PERFORMING RETURN_BAPI_MATERIAL ON END OF TASK
Did you include the exceptions on the call function statement?
exceptions resource_failure = 1
others = 2.
When there are no more free resources on the Group you will get a 'resource_failure' exception. I would expect a resource failure error based on your description.
Check the settings in Rz12 of for the server group you are using.
The ability to "start new task" (via rfc) checks these parameter settings.
So if the server was "busy" then the call fails.

Here is a basic shell program you can use to test parallel code behavior.
REPORT ZPARA.
data t type i.
parameters:
cnt type i default 10000000,
packets type i default 10,
para as checkbox default 'X',
waittime type i default 1,
rfcgroup like rzllitab-classname .
data total type f.
data splits type i.
data mod type i.
data packetsize type i.
data: first type i.
data: last type i.
data: this_last type i.
data: this_split type i.
data: ret_splits type i.
data: act_splits type i.
data: taskname type num8 value 0.
data: begin of task occurs 0,
taskname type num8,
uzeit like sy-uzeit,
wpinfo TYPE WPINFO,
end of task.
data max_pbt_wps type i.
data free_pbt_wps type i.
start-of-selection.
PERFORM classic_version.
form classic_version.
**** TO USE THIS CODE,
* create a parallel(rfc) server group in RZ12.
* create the function decribed below
**************************************************
* set run time res low, so measurement in milli not microsecs.
* we are measure BIG runs
refresh task.
set run time clock resolution low.
get run time field t.
total = 0.
check cnt > 0.
if packets = 0.
packets = 5.
endif.
* splits calculated based on packet size
splits = packets.
PACKETSIZE = cnt div PACKETS.
* is parallel mode selected
if para = 'X'.
* parallel processing MUST be initialized first.
call function 'SPBT_INITIALIZE'
exporting
group_name = rfcgroup
importing
max_pbt_wps = max_pbt_wps
free_pbt_wps = free_pbt_wps
exceptions
invalid_group_name = 1
internal_error = 2
pbt_env_already_initialized = 3
currently_no_resources_avail = 4
no_pbt_resources_found = 5
cant_init_different_pbt_groups = 6
others = 7
.
if sy-subrc <> 0.
* if our group failed or the available processes was 0,
* we would exit cleanly here.
* for the demo, just bomb out.
endif.
last = 0.
ret_splits = 0.
act_splits = 0.
* so for each split
do splits times.
* make a jobname
taskname = taskname + 1.
* work out which chunk we are processing
* ie were are we up to ??
first = last + 1.
this_last = first + packetsize - 1.
* for info purposes record which split
this_split = sy-index.
* just in case we have the last split,
* calculated adjust target end,
if this_last > cnt.
this_last = cnt.
endif.
* try a dispatch this split.
* this is where more logic is needed.
* here we set a max of a hundred tries to dispatch
* something. IN VERY LARGE jobs,
* a commit work and a more intelligent wait
* might be appropriate.
* we at least wait, 1 then 2 then 3... secs etc
* until we get a look in.
do 100 times.
* inside a parallel ( new ) task
* do a chunk of work.
* NO importing parameters here. The result is returned
* in the receiving function.
* SPECIAL, extra, exceptions are available to control
* success or otherwise of the dispatch.
write: / 'Split ', this_split, 'dispatch at', sy-uzeit.
call function 'Z_CHUNK' starting new task taskname
destination in group rfcgroup
performing finished on end of task
exporting
from_int = first
to_int = this_last
exceptions
resource_failure = 1
others = 2.
if sy-subrc = 0.
* dispatch ok, record the fact and march on...
act_splits = act_splits + 1.
last = first + PACKETSIZE - 1.
exit. " the retry until dispatched loop.
else.
write: 'No resource free'.
write: / 'Split ', this_split, 'Waits ', waittime, 'secs at',
sy-uzeit.
* wait x seconds, each attempt waits successlively longer
wait up to waittime seconds.
endif.
enddo.
* Actual failure to dispatch is not captured in this version
* your code should consider this issue.
enddo.
*** THE BIG ROUNDUP
* we wait here until ALL splits are returned.
* we do that by waiting until a return counter
* equals the numbers of dispatches.
* this wait does not wait indefinitely if a dispatch above
* fails, since another continue point is NO oustanding
* async tasks.
***
wait until ret_splits >= act_splits.
else.
call function 'Z_CHUNK'
exporting
from_int = 1
to_int = cnt
importing
tot = total.
endif.
get run time field t.
t = t / 1000.
loop at task.
write: / 'Received task', task-taskname, ' at ', task-uzeit, ' in WP:', task-WPINFO-WP_INDEX.
endloop.
write: / 'Parallel', para.
write: / 'Time ms ', t left-justified.
write: / 'Splits ', splits left-justified.
write: / 'Total ', total left-justified.
endform.
*---------------------------------------------------------------------*
* FORM finished *
*---------------------------------------------------------------------*
* ........ *
*---------------------------------------------------------------------*
* --> TASKNAME *
*---------------------------------------------------------------------*
form finished using taskname.
data: ls_wpinfo type WPINFO.
data l_tot type f.
receive results from function 'Z_CHUNK'
importing
tot = l_tot
wp_index = LS_WPINFO-WP_INDEX.
* when receiving a task back, we get out result
* and record that the task returned, by uping a counter.
task-taskname = taskname.
task-uzeit = sy-uzeit.
task-WPINFO = LS_WPINFO.
append task.
total = total + l_tot.
ret_splits = ret_splits + 1.
endform.
*****
* Create this function to test.
FUNCTION Z_CHUNK
IMPORTING
VALUE(FROM_INT) TYPE I
VALUE(TO_INT) TYPE I
EXPORTING
VALUE(TOT) TYPE F
VALUE(WP_INDEX) TYPE WPINFO-WP_INDEX.
data l_i type i.
tot = 0.
check to_int > from_int.
l_i = from_int.
while l_i <= to_int.
tot = tot + l_i.
l_i = l_i + 1.
endwhile.
CALL FUNCTION 'TH_GET_OWN_WP_NO'
IMPORTING
WP_INDEX = WP_INDEX
.
ENDFUNCTION.