When using a PublishSubject in conjunction with blockingGet(), there seems to be a race condition where a subscriber does not receive the events.
I attached a basic JUnit test in Kotlin, which has 2 methods.
rxTestBroken() shows the broken behavior with a PublishSubject.
rxTestOk() shows that everything works fine with a BehaviorSubject, because the latter replays the last event in case the subscriber is not subscribed in time.
Where does this race condition come from and is using a BehaviorSubject the correct fix?
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.concurrent.TimeUnit
class StateMachine(val stateSubject: Subject<Int>) {
companion object {
val STATE_IDLE = 1
val STATE_READY = 2
}
val output = 10L
var currentState = STATE_IDLE
fun scheduleNextState(nextState: Int) {
Thread(Runnable {
currentState = nextState
stateSubject.onNext(currentState)
}).start()
}
fun start() = scheduleNextState(STATE_READY)
fun stop() = scheduleNextState(STATE_IDLE)
}
class RxTest {
fun stateOutput(stateSubject: Subject<Int>): Single<Int> {
val stateMachine = StateMachine(stateSubject)
val waitForIdle = stateSubject
.startWith(stateMachine.currentState)
.doOnNext {
if (it != StateMachine.STATE_IDLE) { stateMachine.stop() }
}
.filter { it == StateMachine.STATE_IDLE }
.firstOrError()
val scanFile = stateSubject
.doOnSubscribe { stateMachine.start() }
.filter {
when (it) {
StateMachine.STATE_READY -> true
StateMachine.STATE_IDLE -> false
else -> throw RuntimeException("Wrong state $it")
}
}
.firstOrError()
.map { stateMachine.output.toInt() }
.doFinally { stateMachine.stop() }
return waitForIdle.flatMap { scanFile }.timeout(1, TimeUnit.SECONDS).onErrorReturnItem(-1)
}
#Test
fun rxTestBroken() {
for (i in 1..10000) {
assertThat(stateOutput(PublishSubject.create<Int>()).blockingGet())
.withFailMessage("worked $i times")
.isEqualTo(10)
}
}
#Test
fun rxTestOk() {
for (i in 1..10000) {
assertThat(stateOutput(BehaviorSubject.createDefault(StateMachine.STATE_IDLE)).blockingGet())
.withFailMessage("worked $i times")
.isEqualTo(10)
}
}
}
Related
I'm getting data from Marvel API, so the main screen you have different kinds of categories (Characters, Events, Comics etc.) When the user clicks on one of the categories, the app navigates to a list of the related data.
So I want this screen to hold different kinds of data (categories) without using a different screen for each one. Is this the best approach? and how can I do that?
code:
#kotlinx.serialization.Serializable
data class MarvelResponse(
val data:Data
)
#kotlinx.serialization.Serializable
data class Data(
var characters:List<Character>,
var series:List<Series>,
var stories:List<Story>,
var events:List<Event>,
var comics:List<Comic>,
var cartoons:List<Cartoon>
)
class DetailsViewModel #Inject constructor(
private val useCase: UseCase,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _uiState = mutableStateOf<Resource<Any>>(Resource.Loading())
val uiState = _uiState
private fun getData(category: String) {
when (category) {
"Characters" -> {
getCharacters()
}
"Comics" -> {
getComics()
}
"Series" -> {
//
}
"Stories" -> {
//
}
}
}
private fun getCharacters() {
viewModelScope.launch {
val charactersResponse = useCase.getCharactersUseCase()
_uiState.value = Resource.Success(charactersResponse)
}
}
..........
fun Details(
vm: DetailsViewModel = hiltViewModel(),
navController:NavHostController
) {
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = {
navController.popBackStack()
}) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
}
},
title = { Text(text = "Back") }
)
}
) { paddingValues ->
DetailsVerticalGrid(state, modifier = Modifier.padding(paddingValues))
}
}
#ExperimentalMaterialApi
#ExperimentalComposeUiApi
#Composable
fun DetailsVerticalGrid(
data: List<Any>,
modifier: Modifier = Modifier
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(30.dp),
modifier = modifier
) {
items(data.size) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
data.forEach {
DetailsGridItemCard(
image = "",
title = it.title
) {
}
}
}
}
}
}
Of course the above code will not work, I want it to work with any type of data, using a state that holds the data according to the category selected. How can I achieve that?
i wonder is there any way to import dynamic from node-modules?
for example import(ace-builds/src-noconflict/mode-${file} for Ace-editor
With webpack there are two possibilities. First is to use import() by overwriting ace.config.loadModule
import ace from "ace-builds"
ace.config.setDefaultValue("session", "useWorker", false)
ace.config.loadModule = function(moduleName, onLoad) {
var module, moduleType;
if (Array.isArray(moduleName)) {
moduleType = moduleName[0];
moduleName = moduleName[1];
}
var done = m=>{
console.log(moduleName, "loaded")
onLoad && onLoad(m)
};
console.log(moduleName)
var parts = moduleName.split("/");
if (parts[1] == "ext") {
import(`ace-builds/src-noconflict/ext-${parts[2]}`).then(done);
} else if (parts[1] == "theme") {
import(`ace-builds/src-noconflict/theme-${parts[2]}`).then(done);
} else if (parts[1] == "mode") {
import(`ace-builds/src-noconflict/mode-${parts[2]}`).then(done);
} else if (parts[1] == "keyboard") {
import(`ace-builds/src-noconflict/keybinding-${parts[2]}`).then(done);
} else if (parts[1] == "snippets") {
import(`ace-builds/src-noconflict/snippets/${parts[2]}`).then(done);
} else {
console.error(moduleName, "not implemented")
}
}
the second is to use file-loader by adding
import "ace-builds"
import "ace-builds/webpack-resolver"
both work with your example when added to https://github.com/saba-bg/Ace-editor-dynamic-mode-import/blob/016928b521ebc5fd5b39c52352dafddd99d9df6b/src/editor/Ace.js#L7, instead of languages.map part
I am trying to write a custom data type binding for PGInterval and Duration to use jOOQ together with TimescaleDB. Sadly jOOQ does not use it when generating functions for the database routines.
Here's my binding class:
import org.jooq.*
import org.jooq.conf.ParamType
import org.jooq.impl.DSL
import org.postgresql.util.PGInterval
import java.sql.SQLFeatureNotSupportedException
import java.sql.Types
import java.time.Duration
import java.util.*
class PostgresIntervalDurationBinding: Binding<Any, Duration> {
override fun converter(): Converter<Any, Duration> {
return object : Converter<Any, Duration> {
override fun from(t: Any?): Duration {
return if (t == null) Duration.ZERO else Duration.ofSeconds(pgIntervalToSeconds(t as PGInterval))
}
override fun to(u: Duration?): Any? {
return if (u == null || u === Duration.ZERO) null else PGInterval().seconds = u.seconds.toDouble()
}
override fun fromType(): Class<Any> {
return Any::class.java
}
override fun toType(): Class<Duration> {
return Duration::class.java
}
}
}
override fun sql(ctx: BindingSQLContext<Duration>?) {
if (ctx?.render()?.paramType() == ParamType.INLINED)
ctx.render()?.visit(DSL.inline(ctx.convert(converter()).value()))?.sql("::interval")
else
ctx?.render()?.sql("?::interval")
}
override fun register(ctx: BindingRegisterContext<Duration>?) {
ctx?.statement()?.registerOutParameter(ctx.index(), Types.VARCHAR)
}
override fun set(ctx: BindingSetStatementContext<Duration>?) {
ctx?.statement()?.setString(ctx.index(), Objects.toString(ctx.convert(converter()).value(), null))
}
override fun get(ctx: BindingGetResultSetContext<Duration>?) {
ctx?.convert(converter())?.value(ctx.resultSet().getString(ctx.index()))
}
override fun get(ctx: BindingGetStatementContext<Duration>?) {
ctx?.convert(converter())?.value(ctx.statement().getString(ctx.index()))
}
override fun set(ctx: BindingSetSQLOutputContext<Duration>?) {
throw SQLFeatureNotSupportedException()
}
override fun get(ctx: BindingGetSQLInputContext<Duration>?) {
throw SQLFeatureNotSupportedException()
}
companion object {
fun pgIntervalToSeconds(t: PGInterval): Long {
var seconds = 0L
with(t){
seconds += Duration.ofSeconds(this.seconds.toLong()).seconds
seconds += Duration.ofMinutes(this.minutes.toLong()).seconds
seconds += Duration.ofHours(this.hours.toLong()).seconds
seconds += Duration.ofDays(this.days.toLong()).seconds
if (months > 0 || years > 0) throw SQLFeatureNotSupportedException()
}
return seconds
}
}
}
This is my configuration in the pom:
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
<includes>.*</includes>
<inputSchema>public</inputSchema>
<excludes>set_adaptive_chunking
| flyway_schema_history
</excludes>
<forcedTypes>
<forcedType>
<userType>java.time.Duration</userType>
<binding>de.ninjaneers.dmc.jooq.databindings.PostgresIntervalDurationBinding
</binding>
<expression>.*interval.*</expression>
<types>.*</types>
</forcedType>
</forcedTypes>
</database>
For example I expect jOOQ to generate the routine
time_bucket(bucket_with interval, ts timestamp with time zone)
as
timeBucket(Field<Duration> bucketWidth, Field<Timestamp> ts)
but I get
timeBucket(Field<Object> bucketWidth, Field<Timestamp> ts)
You are confusing the two configuration flags:
<expression>.*interval.*</expression>
<types>.*</types>
<expression> is a regex matching the identifier of the column/attribute/parameter whose type you would like to replace. <types> matches the data types. This is what you want:
<types>.*interval.*</types>
<expression>.*</expression>
I put together the code below; the intent was to have a non-blocking server accept a connection and then pass off this connection to an actor for further processing. This works the first time through, but on the subsequent request the program freezes at conServ ! servSoc.accept. Any ideas why this is happening?
import java.net._
import java.io._
import java.nio._
import java.nio.channels._
import java.util._
import scala.actors.Actor
import scala.actors.Actor._
def test() = {
var channel: ServerSocketChannel = null
val isa: InetSocketAddress = new InetSocketAddress(23)
val conServ = actor {
react {
case conn: Socket => {
try {
var pw: PrintWriter = new PrintWriter(conn.getOutputStream(), true)
pw.println("Current time: " + new Date)
pw.close
conn.close
} catch {
case ioe: IOException => println("IOException: " + ioe.getMessage)
case e: Exception => println("Exception: " + e.getMessage)
}
}
}
}
try {
channel = ServerSocketChannel.open
channel.configureBlocking(false)
channel.socket().bind(isa)
var selector: Selector = Selector.open
channel.register(selector, SelectionKey.OP_ACCEPT)
println("** Server ready for requests **")
while (true) {
if (selector.select > 0) {
var selKeys: Set[SelectionKey] = selector.selectedKeys
var selIt: Iterator[SelectionKey] = selKeys.iterator
while (selIt.hasNext) {
var key: SelectionKey = selIt.next.asInstanceOf[SelectionKey]
selIt.remove
if (key.isAcceptable) {
var ssc: ServerSocketChannel = key.channel.asInstanceOf[ServerSocketChannel]
var servSoc: ServerSocket = ssc.socket
try {
conServ ! servSoc.accept
} catch {
case ioe: IOException => println(ioe.printStackTrace)
}
}
}
} else {
continue
}
}
} catch {
case ioe: IOException => println("Could not listen to port 23. " + ioe.printStackTrace)
case e: Exception => println("Error: " + e.printStackTrace)
}
}
test
Enclose your react in a loop block like this:
val conServ = actor {
loop {
react {
// ...
}
}
}
What happens now, is that your actor is started, processes the first message and is not "reacting" again to process additional message from its queue.
See An actor's act method that uses loop.
This is what an actor do, treating one message at the time. What you want is a separate thread to handle each request. For this you can try using Futures.
//Main.scala
/* imports */
object Main extends SimpleSwingApplication {
lazy val ui = new TabbedPane {
/* contents */
}
def top = new MainFrame {
/* contents */
}
override def startup(args: Array[String]) {
val t = top
val loginStatus = new Login(t).status
if (loginStatus == true) {
if (t.size == new Dimension(0, 0)) t.pack
t.visible = true
} else
quit
}
}
//Login.scala
class Login(owner: Window) extends Dialog(owner) {
import Login._
var status = true
contents = ui
listenTo(login) //login is a Button
reactions += {
case ButtonClicked(login) => {
if (/* login field is empty */)
status = false
else if (/* login info is correct */)
status = true
else /*login info is wrong*/
status = false
}
}
}
How can I have 'Main' to wait for 'Login' before displaying itself?
Use a CountDownLatch:
//Main.scala
/* imports */
object Main extends SimpleSwingApplication {
private val latch = new CountDownLatch(1)
lazy val ui = new TabbedPane {
/* contents */
}
def top = new MainFrame {
/* contents */
}
override def startup(args: Array[String]) {
val t = top
val loginDialog = new Login(t, latch)
latch.await
val loginStatus = loginWindow.status
if (loginStatus == true) {
if (t.size == new Dimension(0, 0)) { t.pack }
t.visible = true
} else
quit
}
}
//Login.scala
class Login(owner: Window, latch: CountDownLatch) extends Dialog(owner) {
import Login._
var status = true
contents = ui
listenTo(login) //login is a Button
reactions += {
case ButtonClicked(login) => {
latch.countdown
if (/* login field is empty */)
status = false
else if (/* login info is correct */)
status = true
else /*login info is wrong*/
status = false
}
}
}