@@ -43,7 +43,8 @@ final class PcscReaderAdapter
4343
4444 private static final Logger logger = LoggerFactory .getLogger (PcscReaderAdapter .class );
4545
46- private final CardTerminal terminal ;
46+ private final CardTerminal communicationTerminal ; // For connect/transmit operations
47+ private final CardTerminal monitoringTerminal ; // For waitForCardPresent/Absent operations
4748 private final String name ;
4849 private final PcscPluginAdapter pluginAdapter ;
4950 private final boolean isWindows ;
@@ -67,11 +68,66 @@ final class PcscReaderAdapter
6768 */
6869 PcscReaderAdapter (
6970 CardTerminal terminal , PcscPluginAdapter pluginAdapter , int cardMonitoringCycleDuration ) {
70- this .terminal = terminal ;
71+ this .communicationTerminal = terminal ;
7172 this .pluginAdapter = pluginAdapter ;
7273 this .name = terminal .getName ();
7374 this .isWindows = System .getProperty ("os.name" ).toLowerCase ().contains ("win" );
7475 this .cardMonitoringCycleDuration = cardMonitoringCycleDuration ;
76+
77+ // Create a separate PC/SC context for monitoring operations to avoid contention under Linux
78+ // This is critical because Linux pcsc-lite does not handle concurrent access to a single
79+ // SCARDCONTEXT as robustly as Windows (see threading differences documentation)
80+ this .monitoringTerminal = createMonitoringTerminal (terminal .getName ());
81+ }
82+
83+ /**
84+ * Creates a separate CardTerminal instance for monitoring operations using a dedicated PC/SC
85+ * context.
86+ *
87+ * <p>Under Linux with pcsc-lite, sharing the same SCARDCONTEXT between blocking monitoring calls
88+ * (waitForCardPresent/Absent) and communication operations (transmit) can cause thread contention
89+ * and SCARD_E_SHARING_VIOLATION errors due to the self-pipe trick mechanism used for
90+ * cancellation.
91+ *
92+ * <p>This method attempts to create a new TerminalFactory instance to obtain a separate context.
93+ * If this fails (e.g., on older JRE versions or with certain security providers), it falls back
94+ * to using the same terminal, which may cause issues on Linux but will still work on Windows.
95+ *
96+ * @param terminalName The name of the terminal to create a monitoring instance for.
97+ * @return A CardTerminal instance for monitoring, either with a separate context or the same one.
98+ */
99+ private CardTerminal createMonitoringTerminal (String terminalName ) {
100+ try {
101+ // Attempt to create a new TerminalFactory instance to get a separate PC/SC context
102+ TerminalFactory monitoringFactory = TerminalFactory .getDefault ();
103+ CardTerminals monitoringTerminals = monitoringFactory .terminals ();
104+
105+ // Find the terminal with the same name in the new context
106+ for (CardTerminal t : monitoringTerminals .list ()) {
107+ if (t .getName ().equals (terminalName )) {
108+ if (logger .isDebugEnabled ()) {
109+ logger .debug (
110+ "Reader [{}]: created separate monitoring context for improved Linux compatibility" ,
111+ terminalName );
112+ }
113+ return t ;
114+ }
115+ }
116+
117+ // Terminal not found in new context, fall back to same terminal
118+ logger .warn (
119+ "Reader [{}]: could not find terminal in separate context, using shared context (may cause issues on Linux)" ,
120+ terminalName );
121+ return communicationTerminal ;
122+
123+ } catch (Exception e ) {
124+ // Failed to create separate context, fall back to same terminal
125+ logger .warn (
126+ "Reader [{}]: could not create separate monitoring context ({}), using shared context (may cause issues on Linux)" ,
127+ terminalName ,
128+ e .getMessage ());
129+ return communicationTerminal ;
130+ }
75131 }
76132
77133 /**
@@ -94,7 +150,7 @@ public void waitForCardInsertion() throws TaskCanceledException, ReaderIOExcepti
94150
95151 try {
96152 while (loopWaitCard .get ()) {
97- if (terminal .waitForCardPresent (cardMonitoringCycleDuration )) {
153+ if (monitoringTerminal .waitForCardPresent (cardMonitoringCycleDuration )) {
98154 // card inserted
99155 if (logger .isTraceEnabled ()) {
100156 logger .trace ("Reader [{}]: card inserted" , getName ());
@@ -227,7 +283,7 @@ public void openPhysicalChannel() throws ReaderIOException, CardIOException {
227283 logger .debug (
228284 "Reader [{}]: open card physical channel for protocol [{}]" , getName (), protocol );
229285 }
230- card = this .terminal .connect (protocol );
286+ card = this .communicationTerminal .connect (protocol );
231287 if (isModeExclusive ) {
232288 card .beginExclusive ();
233289 if (logger .isDebugEnabled ()) {
@@ -326,7 +382,7 @@ private static int getDisposition(DisconnectionMode mode) {
326382 private void resetReaderState () {
327383 try {
328384 if (disconnectionMode == DisconnectionMode .UNPOWER ) {
329- terminal .connect ("*" ).disconnect (false );
385+ communicationTerminal .connect ("*" ).disconnect (false );
330386 }
331387 } catch (CardException e ) {
332388 // NOP
@@ -351,7 +407,7 @@ public boolean isPhysicalChannelOpen() {
351407 @ Override
352408 public boolean checkCardPresence () throws ReaderIOException {
353409 try {
354- boolean isCardPresent = terminal .isCardPresent ();
410+ boolean isCardPresent = communicationTerminal .isCardPresent ();
355411 closePhysicalChannelSafely ();
356412 return isCardPresent ;
357413 } catch (CardException e ) {
@@ -517,7 +573,7 @@ private void waitForCardRemovalByPolling() {
517573 private void waitForCardRemovalStandard () throws ReaderIOException {
518574 try {
519575 while (loopWaitCardRemoval .get ()) {
520- if (terminal .waitForCardAbsent (cardMonitoringCycleDuration )) {
576+ if (monitoringTerminal .waitForCardAbsent (cardMonitoringCycleDuration )) {
521577 return ;
522578 }
523579 if (Thread .interrupted ()) {
@@ -623,7 +679,7 @@ public byte[] transmitControlCommand(int commandId, byte[] command) {
623679 if (card != null ) {
624680 response = card .transmitControlCommand (controlCode , command );
625681 } else {
626- Card virtualCard = terminal .connect ("DIRECT" );
682+ Card virtualCard = communicationTerminal .connect ("DIRECT" );
627683 response = virtualCard .transmitControlCommand (controlCode , command );
628684 virtualCard .disconnect (false );
629685 }
0 commit comments